Initial commit Change-Id: I5f41c4cc0135d6efad2d01aeafb4a6954421c082
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b16bd94 --- /dev/null +++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@ +# How to contribute + +We'd love to accept your patches and contributions to this project. + +## Before you begin + +### Sign our Contributor License Agreement + +Contributions to this project must be accompanied by a +[Contributor License Agreement](https://cla.developers.google.com/about) (CLA). +You (or your employer) retain the copyright to your contribution; this simply +gives us permission to use and redistribute your contributions as part of the +project. + +If you or your current employer have already signed the Google CLA (even if it +was for a different project), you probably don't need to do it again. + +Visit <https://cla.developers.google.com/> to see your current agreements or to +sign a new one. + +### Review our community guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). + +## Contribution process + +### Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests.
diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9fdd966 --- /dev/null +++ b/LICENSE
@@ -0,0 +1,26 @@ +Copyright 2024 Google LLC + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac98bc6 --- /dev/null +++ b/README.md
@@ -0,0 +1,6 @@ +# open2opaque + +The program open2opaque migrates Go code using Go Protobuf from the Open API +to the Opaque API. + +See https://opaque-preview.stapelberg.ch/go.dev/blog/protobuf-opaque for context.
diff --git a/gen.go b/gen.go new file mode 100644 index 0000000..272bc7d --- /dev/null +++ b/gen.go
@@ -0,0 +1,8 @@ +// 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 main + +//go:generate go run genprotos.go +//go:generate go run gentestdata.go
diff --git a/genprotos.go b/genprotos.go new file mode 100644 index 0000000..c3b3aac --- /dev/null +++ b/genprotos.go
@@ -0,0 +1,70 @@ +// 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. + +//go:build ignore + +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" +) + +func genProtos() error { + for _, pb := range []struct { + Dir string + Basename string + Package string + }{ + { + Dir: "internal/apiflagdata", + Basename: "go_api_enum.proto", + Package: "google.golang.org/open2opaque/internal/apiflagdata", + }, + + { + Dir: "internal/dashboard", + Basename: "stats.proto", + Package: "google.golang.org/open2opaque/internal/dashboard", + }, + + { + Dir: "internal/fix/testdata/proto2test_go_proto", + Basename: "proto2test.proto", + Package: "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto", + }, + + { + Dir: "internal/fix/testdata/proto3test_go_proto", + Basename: "proto3test.proto", + Package: "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto", + }, + } { + log.Printf("protoc %s/%s", pb.Dir, pb.Basename) + protoc := exec.Command("protoc", + "-I=.", + "--go_out=.", + "--go_opt=M"+pb.Basename+"="+pb.Package, + "--go_opt=paths=source_relative") + if strings.HasSuffix(pb.Dir, "/proto3test_go_proto") { + protoc.Args = append(protoc.Args, "--go_opt=default_api_level=API_HYBRID") + } + protoc.Args = append(protoc.Args, pb.Basename) + protoc.Dir = pb.Dir + protoc.Stderr = os.Stderr + if err := protoc.Run(); err != nil { + return fmt.Errorf("%v: %v", protoc.Args, err) + } + } + return nil +} + +func main() { + if err := genProtos(); err != nil { + log.Fatal(err) + } +}
diff --git a/gentestdata.go b/gentestdata.go new file mode 100644 index 0000000..05d8291 --- /dev/null +++ b/gentestdata.go
@@ -0,0 +1,32 @@ +// 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. + +//go:build ignore + +package main + +import ( + "log" + "os" + + "google.golang.org/open2opaque/internal/fix" +) + +func genTestdataFake() error { + log.Printf("generating testdata/fake") + content := fix.NewSrc("", "") + if err := os.MkdirAll("internal/fix/testdata/fake", 0777); err != nil { + return err + } + if err := os.WriteFile("internal/fix/testdata/fake/fake.go", []byte(content), 0666); err != nil { + return err + } + return nil +} + +func main() { + if err := genTestdataFake(); err != nil { + log.Fatal(err) + } +}
diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0cdd9dd --- /dev/null +++ b/go.mod
@@ -0,0 +1,22 @@ +module google.golang.org/open2opaque + +go 1.23 + +require ( + github.com/dave/dst v0.27.3 + github.com/golang/glog v1.2.2 + github.com/google/go-cmp v0.6.0 + github.com/google/subcommands v1.2.0 + github.com/jhump/protoreflect v1.17.0 + github.com/kylelemons/godebug v1.1.0 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/sync v0.8.0 + golang.org/x/tools v0.25.0 + google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74 +) + +require ( + github.com/bufbuild/protocompile v0.14.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + golang.org/x/mod v0.21.0 // indirect +)
diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9d316b3 --- /dev/null +++ b/go.sum
@@ -0,0 +1,48 @@ +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= +github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= +github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74 h1:lM8vZMBh1xiYK60RmQQEO8xOdsMF+SdeT3BJk1+xMpI= +google.golang.org/protobuf v1.35.3-0.20241211112313-560503ec5d74/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/apiflagdata/go_api_enum.pb.go b/internal/apiflagdata/go_api_enum.pb.go new file mode 100644 index 0000000..f425e28 --- /dev/null +++ b/internal/apiflagdata/go_api_enum.pb.go
@@ -0,0 +1,146 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: go_api_enum.proto + +package apiflagdata + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GoAPI int32 + +const ( + GoAPI_GO_API_UNSPECIFIED GoAPI = 0 + GoAPI_INVALID GoAPI = 1 + GoAPI_OPEN_V1 GoAPI = 2 + GoAPI_OPEN_TO_OPAQUE_HYBRID GoAPI = 3 + GoAPI_OPAQUE_V0 GoAPI = 4 +) + +// Enum value maps for GoAPI. +var ( + GoAPI_name = map[int32]string{ + 0: "GO_API_UNSPECIFIED", + 1: "INVALID", + 2: "OPEN_V1", + 3: "OPEN_TO_OPAQUE_HYBRID", + 4: "OPAQUE_V0", + } + GoAPI_value = map[string]int32{ + "GO_API_UNSPECIFIED": 0, + "INVALID": 1, + "OPEN_V1": 2, + "OPEN_TO_OPAQUE_HYBRID": 3, + "OPAQUE_V0": 4, + } +) + +func (x GoAPI) Enum() *GoAPI { + p := new(GoAPI) + *p = x + return p +} + +func (x GoAPI) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (GoAPI) Descriptor() protoreflect.EnumDescriptor { + return file_go_api_enum_proto_enumTypes[0].Descriptor() +} + +func (GoAPI) Type() protoreflect.EnumType { + return &file_go_api_enum_proto_enumTypes[0] +} + +func (x GoAPI) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use GoAPI.Descriptor instead. +func (GoAPI) EnumDescriptor() ([]byte, []int) { + return file_go_api_enum_proto_rawDescGZIP(), []int{0} +} + +var File_go_api_enum_proto protoreflect.FileDescriptor + +var file_go_api_enum_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x67, 0x6f, 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x65, 0x6e, 0x75, 0x6d, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x25, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x61, + 0x70, 0x69, 0x66, 0x6c, 0x61, 0x67, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x69, 0x0a, 0x05, 0x47, 0x6f, + 0x41, 0x50, 0x49, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, + 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x50, 0x45, 0x4e, + 0x5f, 0x56, 0x31, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x54, 0x4f, + 0x5f, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45, 0x5f, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x10, 0x03, + 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x50, 0x41, 0x51, 0x55, 0x45, 0x5f, 0x56, 0x30, 0x10, 0x04, 0x22, + 0x04, 0x08, 0x05, 0x10, 0x05, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, + 0xe8, 0x07, +} + +var ( + file_go_api_enum_proto_rawDescOnce sync.Once + file_go_api_enum_proto_rawDescData = file_go_api_enum_proto_rawDesc +) + +func file_go_api_enum_proto_rawDescGZIP() []byte { + file_go_api_enum_proto_rawDescOnce.Do(func() { + file_go_api_enum_proto_rawDescData = protoimpl.X.CompressGZIP(file_go_api_enum_proto_rawDescData) + }) + return file_go_api_enum_proto_rawDescData +} + +var file_go_api_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_go_api_enum_proto_goTypes = []any{ + (GoAPI)(0), // 0: net.proto2.go.open2opaque.apiflagdata.GoAPI +} +var file_go_api_enum_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_go_api_enum_proto_init() } +func file_go_api_enum_proto_init() { + if File_go_api_enum_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_go_api_enum_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_go_api_enum_proto_goTypes, + DependencyIndexes: file_go_api_enum_proto_depIdxs, + EnumInfos: file_go_api_enum_proto_enumTypes, + }.Build() + File_go_api_enum_proto = out.File + file_go_api_enum_proto_rawDesc = nil + file_go_api_enum_proto_goTypes = nil + file_go_api_enum_proto_depIdxs = nil +}
diff --git a/internal/apiflagdata/go_api_enum.proto b/internal/apiflagdata/go_api_enum.proto new file mode 100644 index 0000000..7464612 --- /dev/null +++ b/internal/apiflagdata/go_api_enum.proto
@@ -0,0 +1,17 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.apiflagdata; + +enum GoAPI { + reserved 5; + + GO_API_UNSPECIFIED = 0; + INVALID = 1; + OPEN_V1 = 2; + OPEN_TO_OPAQUE_HYBRID = 3; + OPAQUE_V0 = 4; +}
diff --git a/internal/apiflagdata/set/set.go b/internal/apiflagdata/set/set.go new file mode 100644 index 0000000..5db1cfa --- /dev/null +++ b/internal/apiflagdata/set/set.go
@@ -0,0 +1,50 @@ +// 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 set provides set data structures. +package set + +// Strings is a set of strings. +type Strings map[string]struct{} + +// NewStrings constructs Strings with given strs. +func NewStrings(strs ...string) Strings { + ret := Strings{} + for _, s := range strs { + ret.Add(s) + } + return ret +} + +// Add adds given string. +func (s Strings) Add(str string) { + s[str] = struct{}{} +} + +// AddSet adds values from another Strings object. +func (s Strings) AddSet(strs Strings) { + for str := range strs { + s.Add(str) + } +} + +// Len returns the size of the set. +func (s Strings) Len() int { + return len(s) +} + +// Contains returns true if given string is in set, else false. +func (s Strings) Contains(str string) bool { + _, ok := s[str] + return ok +} + +// ToSlice returns a slice of values. +func (s Strings) ToSlice() []string { + ret := make([]string, 0, len(s)) + for k := range s { + ret = append(ret, k) + } + return ret +}
diff --git a/internal/apiflagdata/set/set_test.go b/internal/apiflagdata/set/set_test.go new file mode 100644 index 0000000..33f3324 --- /dev/null +++ b/internal/apiflagdata/set/set_test.go
@@ -0,0 +1,206 @@ +// 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 set_test + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/open2opaque/internal/apiflagdata/set" +) + +var empty = struct{}{} + +func TestNewStrings(t *testing.T) { + testcases := []struct { + input []string + want set.Strings + }{ + { + input: nil, + want: set.Strings{}, + }, + { + input: []string{"hello"}, + want: set.Strings{ + "hello": empty, + }, + }, + { + input: []string{"foo", "bar"}, + want: set.Strings{ + "bar": empty, + "foo": empty, + }, + }, + { + input: []string{"foo", "bar", "foo"}, + want: set.Strings{ + "bar": empty, + "foo": empty, + }, + }, + } + + for _, tc := range testcases { + t.Run("", func(t *testing.T) { + got := set.NewStrings(tc.input...) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("diff -want +got\n%s", diff) + } + }) + } +} + +func TestStringsAdd(t *testing.T) { + testcases := []struct { + s set.Strings + input string + want set.Strings + }{ + { + s: set.NewStrings(), + input: "foo", + want: set.Strings{ + "foo": empty, + }, + }, + { + s: set.NewStrings("foo"), + input: "bar", + want: set.Strings{ + "bar": empty, + "foo": empty, + }, + }, + { + s: set.NewStrings("foo"), + input: "foo", + want: set.Strings{ + "foo": empty, + }, + }, + } + + for _, tc := range testcases { + t.Run("", func(t *testing.T) { + tc.s.Add(tc.input) + if diff := cmp.Diff(tc.want, tc.s); diff != "" { + t.Errorf("diff -want +got\n%s", diff) + } + }) + } +} + +func TestStringsAddSet(t *testing.T) { + testcases := []struct { + s1 set.Strings + s2 set.Strings + want set.Strings + }{ + { + s1: set.NewStrings(), + s2: set.NewStrings("a", "b"), + want: set.Strings{ + "a": empty, + "b": empty, + }, + }, + { + s1: set.NewStrings("a", "b"), + s2: set.NewStrings(), + want: set.Strings{ + "a": empty, + "b": empty, + }, + }, + { + s1: set.NewStrings("a"), + s2: set.NewStrings("b"), + want: set.Strings{ + "a": empty, + "b": empty, + }, + }, + { + s1: set.NewStrings("a", "b"), + s2: set.NewStrings("b", "c"), + want: set.Strings{ + "a": empty, + "b": empty, + "c": empty, + }, + }, + } + + for _, tc := range testcases { + t.Run("", func(t *testing.T) { + tc.s1.AddSet(tc.s2) + if diff := cmp.Diff(tc.want, tc.s1); diff != "" { + t.Errorf("diff -want +got\n%s", diff) + } + }) + } +} + +func TestStringsContains(t *testing.T) { + testcases := []struct { + s set.Strings + input string + want bool + }{ + { + s: set.NewStrings(), + input: "bar", + want: false, + }, + { + s: set.NewStrings("foo"), + input: "foo", + want: true, + }, + { + s: set.NewStrings("bar"), + input: "qux", + want: false, + }, + { + s: set.NewStrings("foo", "bar"), + input: "foo", + want: true, + }, + } + + for _, tc := range testcases { + t.Run("", func(t *testing.T) { + got := tc.s.Contains(tc.input) + if got != tc.want { + t.Errorf("got %v, want %v", got, tc.want) + } + }) + } +} + +func TestStringsSlice(t *testing.T) { + testcases := [][]string{ + {}, + {"a"}, + {"z", "a", "j"}, + } + + for _, input := range testcases { + t.Run("", func(t *testing.T) { + s := set.NewStrings(input...) + got := s.ToSlice() + // Sort the returned slice and input before doing the comparison. + sort.Strings(got) + sort.Strings(input) + if diff := cmp.Diff(input, got); diff != "" { + t.Errorf("diff -want +got\n%s", diff) + } + }) + } +}
diff --git a/internal/dashboard/stats.pb.go b/internal/dashboard/stats.pb.go new file mode 100644 index 0000000..08010a4 --- /dev/null +++ b/internal/dashboard/stats.pb.go
@@ -0,0 +1,2858 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: stats.proto + +//go:build !protoopaque + +package dashboard + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/gofeaturespb" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RewriteLevel represents rewrite level after which entries are +// collected. For example, GREEN means that the tool does a green +// rewrite and analyzes the result to create entries. +type RewriteLevel int32 + +const ( + RewriteLevel_REWRITE_LEVEL_UNSPECIFIED RewriteLevel = 0 + RewriteLevel_NONE RewriteLevel = 1 + RewriteLevel_GREEN RewriteLevel = 2 + RewriteLevel_YELLOW RewriteLevel = 3 + RewriteLevel_RED RewriteLevel = 4 +) + +// Enum value maps for RewriteLevel. +var ( + RewriteLevel_name = map[int32]string{ + 0: "REWRITE_LEVEL_UNSPECIFIED", + 1: "NONE", + 2: "GREEN", + 3: "YELLOW", + 4: "RED", + } + RewriteLevel_value = map[string]int32{ + "REWRITE_LEVEL_UNSPECIFIED": 0, + "NONE": 1, + "GREEN": 2, + "YELLOW": 3, + "RED": 4, + } +) + +func (x RewriteLevel) Enum() *RewriteLevel { + p := new(RewriteLevel) + *p = x + return p +} + +func (x RewriteLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RewriteLevel) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[0].Descriptor() +} + +func (RewriteLevel) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[0] +} + +func (x RewriteLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Status_Type int32 + +const ( + Status_UNSPECIFIED_TYPE Status_Type = 0 + Status_OK Status_Type = 1 + Status_SKIP Status_Type = 2 + Status_FAIL Status_Type = 3 +) + +// Enum value maps for Status_Type. +var ( + Status_Type_name = map[int32]string{ + 0: "UNSPECIFIED_TYPE", + 1: "OK", + 2: "SKIP", + 3: "FAIL", + } + Status_Type_value = map[string]int32{ + "UNSPECIFIED_TYPE": 0, + "OK": 1, + "SKIP": 2, + "FAIL": 3, + } +) + +func (x Status_Type) Enum() *Status_Type { + p := new(Status_Type) + *p = x + return p +} + +func (x Status_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Status_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[1].Descriptor() +} + +func (Status_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[1] +} + +func (x Status_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Use_Type int32 + +const ( + Use_TYPE_UNSPECIFIED Use_Type = 0 + Use_DIRECT_FIELD_ACCESS Use_Type = 1 + Use_METHOD_CALL Use_Type = 2 + Use_CONSTRUCTOR Use_Type = 3 + Use_CONVERSION Use_Type = 4 + Use_TYPE_ASSERTION Use_Type = 5 + Use_TYPE_DEFINITION Use_Type = 6 + Use_EMBEDDING Use_Type = 7 + Use_INTERNAL_FIELD_ACCESS Use_Type = 8 + Use_REFLECT_CALL Use_Type = 9 + Use_SHALLOW_COPY Use_Type = 10 + Use_BUILD_DEPENDENCY Use_Type = 11 +) + +// Enum value maps for Use_Type. +var ( + Use_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "DIRECT_FIELD_ACCESS", + 2: "METHOD_CALL", + 3: "CONSTRUCTOR", + 4: "CONVERSION", + 5: "TYPE_ASSERTION", + 6: "TYPE_DEFINITION", + 7: "EMBEDDING", + 8: "INTERNAL_FIELD_ACCESS", + 9: "REFLECT_CALL", + 10: "SHALLOW_COPY", + 11: "BUILD_DEPENDENCY", + } + Use_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "DIRECT_FIELD_ACCESS": 1, + "METHOD_CALL": 2, + "CONSTRUCTOR": 3, + "CONVERSION": 4, + "TYPE_ASSERTION": 5, + "TYPE_DEFINITION": 6, + "EMBEDDING": 7, + "INTERNAL_FIELD_ACCESS": 8, + "REFLECT_CALL": 9, + "SHALLOW_COPY": 10, + "BUILD_DEPENDENCY": 11, + } +) + +func (x Use_Type) Enum() *Use_Type { + p := new(Use_Type) + *p = x + return p +} + +func (x Use_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Use_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[2].Descriptor() +} + +func (Use_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[2] +} + +func (x Use_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type MethodCall_Type int32 + +const ( + MethodCall_INVALID MethodCall_Type = 0 + MethodCall_GET_ONEOF MethodCall_Type = 1 + MethodCall_GET_BUILD MethodCall_Type = 2 +) + +// Enum value maps for MethodCall_Type. +var ( + MethodCall_Type_name = map[int32]string{ + 0: "INVALID", + 1: "GET_ONEOF", + 2: "GET_BUILD", + } + MethodCall_Type_value = map[string]int32{ + "INVALID": 0, + "GET_ONEOF": 1, + "GET_BUILD": 2, + } +) + +func (x MethodCall_Type) Enum() *MethodCall_Type { + p := new(MethodCall_Type) + *p = x + return p +} + +func (x MethodCall_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MethodCall_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[3].Descriptor() +} + +func (MethodCall_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[3] +} + +func (x MethodCall_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Constructor_Type int32 + +const ( + Constructor_TYPE_UNSPECIFIED Constructor_Type = 0 + Constructor_EMPTY_LITERAL Constructor_Type = 1 + Constructor_NONEMPTY_LITERAL Constructor_Type = 2 + Constructor_BUILDER Constructor_Type = 3 +) + +// Enum value maps for Constructor_Type. +var ( + Constructor_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "EMPTY_LITERAL", + 2: "NONEMPTY_LITERAL", + 3: "BUILDER", + } + Constructor_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "EMPTY_LITERAL": 1, + "NONEMPTY_LITERAL": 2, + "BUILDER": 3, + } +) + +func (x Constructor_Type) Enum() *Constructor_Type { + p := new(Constructor_Type) + *p = x + return p +} + +func (x Constructor_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Constructor_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[4].Descriptor() +} + +func (Constructor_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[4] +} + +func (x Constructor_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Conversion_Context int32 + +const ( + Conversion_CONTEXT_UNSPECIFIED Conversion_Context = 0 + Conversion_CALL_ARGUMENT Conversion_Context = 1 + Conversion_RETURN_VALUE Conversion_Context = 2 + Conversion_ASSIGNMENT Conversion_Context = 3 + Conversion_EXPLICIT Conversion_Context = 4 + Conversion_COMPOSITE_LITERAL_ELEMENT Conversion_Context = 5 + Conversion_CHAN_SEND Conversion_Context = 6 + Conversion_FUNC_RET Conversion_Context = 7 +) + +// Enum value maps for Conversion_Context. +var ( + Conversion_Context_name = map[int32]string{ + 0: "CONTEXT_UNSPECIFIED", + 1: "CALL_ARGUMENT", + 2: "RETURN_VALUE", + 3: "ASSIGNMENT", + 4: "EXPLICIT", + 5: "COMPOSITE_LITERAL_ELEMENT", + 6: "CHAN_SEND", + 7: "FUNC_RET", + } + Conversion_Context_value = map[string]int32{ + "CONTEXT_UNSPECIFIED": 0, + "CALL_ARGUMENT": 1, + "RETURN_VALUE": 2, + "ASSIGNMENT": 3, + "EXPLICIT": 4, + "COMPOSITE_LITERAL_ELEMENT": 5, + "CHAN_SEND": 6, + "FUNC_RET": 7, + } +) + +func (x Conversion_Context) Enum() *Conversion_Context { + p := new(Conversion_Context) + *p = x + return p +} + +func (x Conversion_Context) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Conversion_Context) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[5].Descriptor() +} + +func (Conversion_Context) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[5] +} + +func (x Conversion_Context) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type ShallowCopy_Type int32 + +const ( + ShallowCopy_TYPE_UNSPECIFIED ShallowCopy_Type = 0 + ShallowCopy_ASSIGN ShallowCopy_Type = 1 + ShallowCopy_CALL_ARGUMENT ShallowCopy_Type = 2 + ShallowCopy_FUNC_RET ShallowCopy_Type = 3 + ShallowCopy_COMPOSITE_LITERAL_ELEMENT ShallowCopy_Type = 4 + ShallowCopy_CHAN_SEND ShallowCopy_Type = 5 +) + +// Enum value maps for ShallowCopy_Type. +var ( + ShallowCopy_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "ASSIGN", + 2: "CALL_ARGUMENT", + 3: "FUNC_RET", + 4: "COMPOSITE_LITERAL_ELEMENT", + 5: "CHAN_SEND", + } + ShallowCopy_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "ASSIGN": 1, + "CALL_ARGUMENT": 2, + "FUNC_RET": 3, + "COMPOSITE_LITERAL_ELEMENT": 4, + "CHAN_SEND": 5, + } +) + +func (x ShallowCopy_Type) Enum() *ShallowCopy_Type { + p := new(ShallowCopy_Type) + *p = x + return p +} + +func (x ShallowCopy_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ShallowCopy_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[6].Descriptor() +} + +func (ShallowCopy_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[6] +} + +func (x ShallowCopy_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Entry represents a single usage of a proto type. For example, a +// single field access, method call, use as a type, etc. We collect +// entries and analyze them offline to generate various statistics +// (e.g. number of direct field accesses per package) about the +// migration to opaque Go protocol buffer API. +type Entry struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // If status is not set or empty then all other fields should be set + // and describe a single usage of a protocol buffer type in Go. + // + // If status is not empty then: + // - status.error says what went wrong, and + // - location.package says which package couldn't be processed + Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + // Location in Go code. Always set. Location.package is always + // non-empty. + Location *Location `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"` + // Rewrite level after which this entry was captured. + Level RewriteLevel `protobuf:"varint,3,opt,name=level,enum=net.proto2.go.open2opaque.stats.RewriteLevel" json:"level,omitempty"` + // Go type representing a protocol buffer type. + Type *Type `protobuf:"bytes,4,opt,name=type" json:"type,omitempty"` + // Go expression which leads to this entry. + Expr *Expression `protobuf:"bytes,5,opt,name=expr" json:"expr,omitempty"` + // Describes how a protocol buffer is used (e.g. direct field access). + Use *Use `protobuf:"bytes,6,opt,name=use" json:"use,omitempty"` + // Source of the information. For debugging purposes. + Source *Source `protobuf:"bytes,7,opt,name=source" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Entry) Reset() { + *x = Entry{} + mi := &file_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Entry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Entry) ProtoMessage() {} + +func (x *Entry) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Entry) GetStatus() *Status { + if x != nil { + return x.Status + } + return nil +} + +func (x *Entry) GetLocation() *Location { + if x != nil { + return x.Location + } + return nil +} + +func (x *Entry) GetLevel() RewriteLevel { + if x != nil { + return x.Level + } + return RewriteLevel_REWRITE_LEVEL_UNSPECIFIED +} + +func (x *Entry) GetType() *Type { + if x != nil { + return x.Type + } + return nil +} + +func (x *Entry) GetExpr() *Expression { + if x != nil { + return x.Expr + } + return nil +} + +func (x *Entry) GetUse() *Use { + if x != nil { + return x.Use + } + return nil +} + +func (x *Entry) GetSource() *Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *Entry) SetStatus(v *Status) { + x.Status = v +} + +func (x *Entry) SetLocation(v *Location) { + x.Location = v +} + +func (x *Entry) SetLevel(v RewriteLevel) { + x.Level = v +} + +func (x *Entry) SetType(v *Type) { + x.Type = v +} + +func (x *Entry) SetExpr(v *Expression) { + x.Expr = v +} + +func (x *Entry) SetUse(v *Use) { + x.Use = v +} + +func (x *Entry) SetSource(v *Source) { + x.Source = v +} + +func (x *Entry) HasStatus() bool { + if x == nil { + return false + } + return x.Status != nil +} + +func (x *Entry) HasLocation() bool { + if x == nil { + return false + } + return x.Location != nil +} + +func (x *Entry) HasType() bool { + if x == nil { + return false + } + return x.Type != nil +} + +func (x *Entry) HasExpr() bool { + if x == nil { + return false + } + return x.Expr != nil +} + +func (x *Entry) HasUse() bool { + if x == nil { + return false + } + return x.Use != nil +} + +func (x *Entry) HasSource() bool { + if x == nil { + return false + } + return x.Source != nil +} + +func (x *Entry) ClearStatus() { + x.Status = nil +} + +func (x *Entry) ClearLocation() { + x.Location = nil +} + +func (x *Entry) ClearType() { + x.Type = nil +} + +func (x *Entry) ClearExpr() { + x.Expr = nil +} + +func (x *Entry) ClearUse() { + x.Use = nil +} + +func (x *Entry) ClearSource() { + x.Source = nil +} + +type Entry_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // If status is not set or empty then all other fields should be set + // and describe a single usage of a protocol buffer type in Go. + // + // If status is not empty then: + // - status.error says what went wrong, and + // - location.package says which package couldn't be processed + Status *Status + // Location in Go code. Always set. Location.package is always + // non-empty. + Location *Location + // Rewrite level after which this entry was captured. + Level RewriteLevel + // Go type representing a protocol buffer type. + Type *Type + // Go expression which leads to this entry. + Expr *Expression + // Describes how a protocol buffer is used (e.g. direct field access). + Use *Use + // Source of the information. For debugging purposes. + Source *Source +} + +func (b0 Entry_builder) Build() *Entry { + m0 := &Entry{} + b, x := &b0, m0 + _, _ = b, x + x.Status = b.Status + x.Location = b.Location + x.Level = b.Level + x.Type = b.Type + x.Expr = b.Expr + x.Use = b.Use + x.Source = b.Source + return m0 +} + +// Location represents location of an expression in a Go file. +type Location struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Full name of a Go package + Package string `protobuf:"bytes,1,opt,name=package" json:"package,omitempty"` + // path to a Go file + File string `protobuf:"bytes,2,opt,name=file" json:"file,omitempty"` + // Whether the file is a generated file. + IsGeneratedFile bool `protobuf:"varint,3,opt,name=is_generated_file,json=isGeneratedFile" json:"is_generated_file,omitempty"` + // Start of the expression. + Start *Position `protobuf:"bytes,4,opt,name=start" json:"start,omitempty"` + // End of the expression. + End *Position `protobuf:"bytes,5,opt,name=end" json:"end,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Location) Reset() { + *x = Location{} + mi := &file_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Location) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Location) ProtoMessage() {} + +func (x *Location) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Location) GetPackage() string { + if x != nil { + return x.Package + } + return "" +} + +func (x *Location) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *Location) GetIsGeneratedFile() bool { + if x != nil { + return x.IsGeneratedFile + } + return false +} + +func (x *Location) GetStart() *Position { + if x != nil { + return x.Start + } + return nil +} + +func (x *Location) GetEnd() *Position { + if x != nil { + return x.End + } + return nil +} + +func (x *Location) SetPackage(v string) { + x.Package = v +} + +func (x *Location) SetFile(v string) { + x.File = v +} + +func (x *Location) SetIsGeneratedFile(v bool) { + x.IsGeneratedFile = v +} + +func (x *Location) SetStart(v *Position) { + x.Start = v +} + +func (x *Location) SetEnd(v *Position) { + x.End = v +} + +func (x *Location) HasStart() bool { + if x == nil { + return false + } + return x.Start != nil +} + +func (x *Location) HasEnd() bool { + if x == nil { + return false + } + return x.End != nil +} + +func (x *Location) ClearStart() { + x.Start = nil +} + +func (x *Location) ClearEnd() { + x.End = nil +} + +type Location_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Full name of a Go package + Package string + // path to a Go file + File string + // Whether the file is a generated file. + IsGeneratedFile bool + // Start of the expression. + Start *Position + // End of the expression. + End *Position +} + +func (b0 Location_builder) Build() *Location { + m0 := &Location{} + b, x := &b0, m0 + _, _ = b, x + x.Package = b.Package + x.File = b.File + x.IsGeneratedFile = b.IsGeneratedFile + x.Start = b.Start + x.End = b.End + return m0 +} + +// Position describes a position in a Go file. +type Position struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Line int64 `protobuf:"varint,1,opt,name=line" json:"line,omitempty"` + Column int64 `protobuf:"varint,2,opt,name=column" json:"column,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Position) Reset() { + *x = Position{} + mi := &file_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Position) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Position) ProtoMessage() {} + +func (x *Position) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Position) GetLine() int64 { + if x != nil { + return x.Line + } + return 0 +} + +func (x *Position) GetColumn() int64 { + if x != nil { + return x.Column + } + return 0 +} + +func (x *Position) SetLine(v int64) { + x.Line = v +} + +func (x *Position) SetColumn(v int64) { + x.Column = v +} + +type Position_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Line int64 + Column int64 +} + +func (b0 Position_builder) Build() *Position { + m0 := &Position{} + b, x := &b0, m0 + _, _ = b, x + x.Line = b.Line + x.Column = b.Column + return m0 +} + +// Status specifies an error that occurred. Empty error indicates +// success. +type Status struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Type Status_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Status_Type" json:"type,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Status) Reset() { + *x = Status{} + mi := &file_stats_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Status) GetType() Status_Type { + if x != nil { + return x.Type + } + return Status_UNSPECIFIED_TYPE +} + +func (x *Status) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *Status) SetType(v Status_Type) { + x.Type = v +} + +func (x *Status) SetError(v string) { + x.Error = v +} + +type Status_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Status_Type + Error string +} + +func (b0 Status_builder) Build() *Status { + m0 := &Status{} + b, x := &b0, m0 + _, _ = b, x + x.Type = b.Type + x.Error = b.Error + return m0 +} + +// Type represents a Go name for a protocol buffer type. +type Type struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The short name of the Go type representing the protocol buffer + // type. For example: "qem_go_proto.QueryEventMessage". + ShortName string `protobuf:"bytes,1,opt,name=short_name,json=shortName" json:"short_name,omitempty"` + // A fully qualified name of the Go type representing the protocol + // buffer type. + LongName string `protobuf:"bytes,2,opt,name=long_name,json=longName" json:"long_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Type) Reset() { + *x = Type{} + mi := &file_stats_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Type) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Type) ProtoMessage() {} + +func (x *Type) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Type) GetShortName() string { + if x != nil { + return x.ShortName + } + return "" +} + +func (x *Type) GetLongName() string { + if x != nil { + return x.LongName + } + return "" +} + +func (x *Type) SetShortName(v string) { + x.ShortName = v +} + +func (x *Type) SetLongName(v string) { + x.LongName = v +} + +type Type_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The short name of the Go type representing the protocol buffer + // type. For example: "qem_go_proto.QueryEventMessage". + ShortName string + // A fully qualified name of the Go type representing the protocol + // buffer type. + LongName string +} + +func (b0 Type_builder) Build() *Type { + m0 := &Type{} + b, x := &b0, m0 + _, _ = b, x + x.ShortName = b.ShortName + x.LongName = b.LongName + return m0 +} + +// Expression describes a Go expression. +type Expression struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // go/ast expression type (e.g. "*ast.Ident"). + Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` + // go/ast expression type of the parent. + ParentType string `protobuf:"bytes,2,opt,name=parent_type,json=parentType" json:"parent_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Expression) Reset() { + *x = Expression{} + mi := &file_stats_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Expression) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Expression) ProtoMessage() {} + +func (x *Expression) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Expression) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Expression) GetParentType() string { + if x != nil { + return x.ParentType + } + return "" +} + +func (x *Expression) SetType(v string) { + x.Type = v +} + +func (x *Expression) SetParentType(v string) { + x.ParentType = v +} + +type Expression_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // go/ast expression type (e.g. "*ast.Ident"). + Type string + // go/ast expression type of the parent. + ParentType string +} + +func (b0 Expression_builder) Build() *Expression { + m0 := &Expression{} + b, x := &b0, m0 + _, _ = b, x + x.Type = b.Type + x.ParentType = b.ParentType + return m0 +} + +// Use describes a use of a protocol buffer type in Go. +type Use struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Type Use_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Use_Type" json:"type,omitempty"` + DirectFieldAccess *FieldAccess `protobuf:"bytes,2,opt,name=direct_field_access,json=directFieldAccess" json:"direct_field_access,omitempty"` + MethodCall *MethodCall `protobuf:"bytes,3,opt,name=method_call,json=methodCall" json:"method_call,omitempty"` + Constructor *Constructor `protobuf:"bytes,4,opt,name=constructor" json:"constructor,omitempty"` + Conversion *Conversion `protobuf:"bytes,5,opt,name=conversion" json:"conversion,omitempty"` + FuncArg *FuncArg `protobuf:"bytes,6,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"` + TypeAssertion *TypeAssertion `protobuf:"bytes,7,opt,name=type_assertion,json=typeAssertion" json:"type_assertion,omitempty"` + TypeDefinition *TypeDefinition `protobuf:"bytes,8,opt,name=type_definition,json=typeDefinition" json:"type_definition,omitempty"` + Embedding *Embedding `protobuf:"bytes,9,opt,name=embedding" json:"embedding,omitempty"` + InternalFieldAccess *FieldAccess `protobuf:"bytes,10,opt,name=internal_field_access,json=internalFieldAccess" json:"internal_field_access,omitempty"` + ReflectCall *ReflectCall `protobuf:"bytes,11,opt,name=reflect_call,json=reflectCall" json:"reflect_call,omitempty"` + ShallowCopy *ShallowCopy `protobuf:"bytes,12,opt,name=shallow_copy,json=shallowCopy" json:"shallow_copy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Use) Reset() { + *x = Use{} + mi := &file_stats_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Use) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Use) ProtoMessage() {} + +func (x *Use) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Use) GetType() Use_Type { + if x != nil { + return x.Type + } + return Use_TYPE_UNSPECIFIED +} + +func (x *Use) GetDirectFieldAccess() *FieldAccess { + if x != nil { + return x.DirectFieldAccess + } + return nil +} + +func (x *Use) GetMethodCall() *MethodCall { + if x != nil { + return x.MethodCall + } + return nil +} + +func (x *Use) GetConstructor() *Constructor { + if x != nil { + return x.Constructor + } + return nil +} + +func (x *Use) GetConversion() *Conversion { + if x != nil { + return x.Conversion + } + return nil +} + +func (x *Use) GetFuncArg() *FuncArg { + if x != nil { + return x.FuncArg + } + return nil +} + +func (x *Use) GetTypeAssertion() *TypeAssertion { + if x != nil { + return x.TypeAssertion + } + return nil +} + +func (x *Use) GetTypeDefinition() *TypeDefinition { + if x != nil { + return x.TypeDefinition + } + return nil +} + +func (x *Use) GetEmbedding() *Embedding { + if x != nil { + return x.Embedding + } + return nil +} + +func (x *Use) GetInternalFieldAccess() *FieldAccess { + if x != nil { + return x.InternalFieldAccess + } + return nil +} + +func (x *Use) GetReflectCall() *ReflectCall { + if x != nil { + return x.ReflectCall + } + return nil +} + +func (x *Use) GetShallowCopy() *ShallowCopy { + if x != nil { + return x.ShallowCopy + } + return nil +} + +func (x *Use) SetType(v Use_Type) { + x.Type = v +} + +func (x *Use) SetDirectFieldAccess(v *FieldAccess) { + x.DirectFieldAccess = v +} + +func (x *Use) SetMethodCall(v *MethodCall) { + x.MethodCall = v +} + +func (x *Use) SetConstructor(v *Constructor) { + x.Constructor = v +} + +func (x *Use) SetConversion(v *Conversion) { + x.Conversion = v +} + +func (x *Use) SetFuncArg(v *FuncArg) { + x.FuncArg = v +} + +func (x *Use) SetTypeAssertion(v *TypeAssertion) { + x.TypeAssertion = v +} + +func (x *Use) SetTypeDefinition(v *TypeDefinition) { + x.TypeDefinition = v +} + +func (x *Use) SetEmbedding(v *Embedding) { + x.Embedding = v +} + +func (x *Use) SetInternalFieldAccess(v *FieldAccess) { + x.InternalFieldAccess = v +} + +func (x *Use) SetReflectCall(v *ReflectCall) { + x.ReflectCall = v +} + +func (x *Use) SetShallowCopy(v *ShallowCopy) { + x.ShallowCopy = v +} + +func (x *Use) HasDirectFieldAccess() bool { + if x == nil { + return false + } + return x.DirectFieldAccess != nil +} + +func (x *Use) HasMethodCall() bool { + if x == nil { + return false + } + return x.MethodCall != nil +} + +func (x *Use) HasConstructor() bool { + if x == nil { + return false + } + return x.Constructor != nil +} + +func (x *Use) HasConversion() bool { + if x == nil { + return false + } + return x.Conversion != nil +} + +func (x *Use) HasFuncArg() bool { + if x == nil { + return false + } + return x.FuncArg != nil +} + +func (x *Use) HasTypeAssertion() bool { + if x == nil { + return false + } + return x.TypeAssertion != nil +} + +func (x *Use) HasTypeDefinition() bool { + if x == nil { + return false + } + return x.TypeDefinition != nil +} + +func (x *Use) HasEmbedding() bool { + if x == nil { + return false + } + return x.Embedding != nil +} + +func (x *Use) HasInternalFieldAccess() bool { + if x == nil { + return false + } + return x.InternalFieldAccess != nil +} + +func (x *Use) HasReflectCall() bool { + if x == nil { + return false + } + return x.ReflectCall != nil +} + +func (x *Use) HasShallowCopy() bool { + if x == nil { + return false + } + return x.ShallowCopy != nil +} + +func (x *Use) ClearDirectFieldAccess() { + x.DirectFieldAccess = nil +} + +func (x *Use) ClearMethodCall() { + x.MethodCall = nil +} + +func (x *Use) ClearConstructor() { + x.Constructor = nil +} + +func (x *Use) ClearConversion() { + x.Conversion = nil +} + +func (x *Use) ClearFuncArg() { + x.FuncArg = nil +} + +func (x *Use) ClearTypeAssertion() { + x.TypeAssertion = nil +} + +func (x *Use) ClearTypeDefinition() { + x.TypeDefinition = nil +} + +func (x *Use) ClearEmbedding() { + x.Embedding = nil +} + +func (x *Use) ClearInternalFieldAccess() { + x.InternalFieldAccess = nil +} + +func (x *Use) ClearReflectCall() { + x.ReflectCall = nil +} + +func (x *Use) ClearShallowCopy() { + x.ShallowCopy = nil +} + +type Use_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Use_Type + DirectFieldAccess *FieldAccess + MethodCall *MethodCall + Constructor *Constructor + Conversion *Conversion + FuncArg *FuncArg + TypeAssertion *TypeAssertion + TypeDefinition *TypeDefinition + Embedding *Embedding + InternalFieldAccess *FieldAccess + ReflectCall *ReflectCall + ShallowCopy *ShallowCopy +} + +func (b0 Use_builder) Build() *Use { + m0 := &Use{} + b, x := &b0, m0 + _, _ = b, x + x.Type = b.Type + x.DirectFieldAccess = b.DirectFieldAccess + x.MethodCall = b.MethodCall + x.Constructor = b.Constructor + x.Conversion = b.Conversion + x.FuncArg = b.FuncArg + x.TypeAssertion = b.TypeAssertion + x.TypeDefinition = b.TypeDefinition + x.Embedding = b.Embedding + x.InternalFieldAccess = b.InternalFieldAccess + x.ReflectCall = b.ReflectCall + x.ShallowCopy = b.ShallowCopy + return m0 +} + +// ReflectCall represents a call to the Go reflection API. +type ReflectCall struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Information about all frames leading to the reflection call. + // + // A callstack usually looks like: + // + // reflect.Value.Field // the function triggering the log + // reflect.F1 // more functions + // ... // in the + // reflect.Fn // reflect package + // import/path.Caller // (1) fn: calls into the reflect package + // import/path.C1 // more functions + // ... // in the same package + // import/path.Cn1 // as fn + // ancestor/import.Ancestor // (2) caller: calls into fn's package + // ... // more frames + // + // The frames field has information about all frames but we also + // store a few selected frames separately to make it easier to write + // queries: + // + // (1) caller is the last function before the reflection package + // (2) ancestor is the last function from a different package than caller + // + // The frames field contains at least one frame (a function in the + // reflect package) but fn and caller may be unset. + Frames []*Frame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"` + Fn *Frame `protobuf:"bytes,2,opt,name=fn" json:"fn,omitempty"` + Caller *Frame `protobuf:"bytes,3,opt,name=caller" json:"caller,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReflectCall) Reset() { + *x = ReflectCall{} + mi := &file_stats_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReflectCall) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReflectCall) ProtoMessage() {} + +func (x *ReflectCall) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ReflectCall) GetFrames() []*Frame { + if x != nil { + return x.Frames + } + return nil +} + +func (x *ReflectCall) GetFn() *Frame { + if x != nil { + return x.Fn + } + return nil +} + +func (x *ReflectCall) GetCaller() *Frame { + if x != nil { + return x.Caller + } + return nil +} + +func (x *ReflectCall) SetFrames(v []*Frame) { + x.Frames = v +} + +func (x *ReflectCall) SetFn(v *Frame) { + x.Fn = v +} + +func (x *ReflectCall) SetCaller(v *Frame) { + x.Caller = v +} + +func (x *ReflectCall) HasFn() bool { + if x == nil { + return false + } + return x.Fn != nil +} + +func (x *ReflectCall) HasCaller() bool { + if x == nil { + return false + } + return x.Caller != nil +} + +func (x *ReflectCall) ClearFn() { + x.Fn = nil +} + +func (x *ReflectCall) ClearCaller() { + x.Caller = nil +} + +type ReflectCall_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Information about all frames leading to the reflection call. + // + // A callstack usually looks like: + // + // reflect.Value.Field // the function triggering the log + // reflect.F1 // more functions + // ... // in the + // reflect.Fn // reflect package + // import/path.Caller // (1) fn: calls into the reflect package + // import/path.C1 // more functions + // ... // in the same package + // import/path.Cn1 // as fn + // ancestor/import.Ancestor // (2) caller: calls into fn's package + // ... // more frames + // + // The frames field has information about all frames but we also + // store a few selected frames separately to make it easier to write + // queries: + // + // (1) caller is the last function before the reflection package + // (2) ancestor is the last function from a different package than caller + // + // The frames field contains at least one frame (a function in the + // reflect package) but fn and caller may be unset. + Frames []*Frame + Fn *Frame + Caller *Frame +} + +func (b0 ReflectCall_builder) Build() *ReflectCall { + m0 := &ReflectCall{} + b, x := &b0, m0 + _, _ = b, x + x.Frames = b.Frames + x.Fn = b.Fn + x.Caller = b.Caller + return m0 +} + +// Frame represents information about a single frame in a Go +// stacktrace. +type Frame struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Fully qualified function name. For example: + // + // net/http.Error + Function string `protobuf:"bytes,1,opt,name=function" json:"function,omitempty"` + // true if the function is exported. + IsExported bool `protobuf:"varint,6,opt,name=is_exported,json=isExported" json:"is_exported,omitempty"` + // Packed in which the function is defined. + Package string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` + // path to the source file defining the function. + // + // For standard-library, the path is relative to the src/ + // directory (e.g. net/http/server.go). + File string `protobuf:"bytes,3,opt,name=file" json:"file,omitempty"` + // Line number, together with file, is the location where function + // is defined. + Line string `protobuf:"bytes,4,opt,name=line" json:"line,omitempty"` + // index of the frame in the reflect_call.frames repeated field. + // + // This exists to make SQL queries easier. + Index int64 `protobuf:"varint,5,opt,name=index" json:"index,omitempty"` + // index of the frame across consecutive frames in the same package. + // + // This exists to make SQL queries easier. + PkgIndex int64 `protobuf:"varint,7,opt,name=pkg_index,json=pkgIndex" json:"pkg_index,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Frame) Reset() { + *x = Frame{} + mi := &file_stats_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Frame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Frame) ProtoMessage() {} + +func (x *Frame) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Frame) GetFunction() string { + if x != nil { + return x.Function + } + return "" +} + +func (x *Frame) GetIsExported() bool { + if x != nil { + return x.IsExported + } + return false +} + +func (x *Frame) GetPackage() string { + if x != nil { + return x.Package + } + return "" +} + +func (x *Frame) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *Frame) GetLine() string { + if x != nil { + return x.Line + } + return "" +} + +func (x *Frame) GetIndex() int64 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Frame) GetPkgIndex() int64 { + if x != nil { + return x.PkgIndex + } + return 0 +} + +func (x *Frame) SetFunction(v string) { + x.Function = v +} + +func (x *Frame) SetIsExported(v bool) { + x.IsExported = v +} + +func (x *Frame) SetPackage(v string) { + x.Package = v +} + +func (x *Frame) SetFile(v string) { + x.File = v +} + +func (x *Frame) SetLine(v string) { + x.Line = v +} + +func (x *Frame) SetIndex(v int64) { + x.Index = v +} + +func (x *Frame) SetPkgIndex(v int64) { + x.PkgIndex = v +} + +type Frame_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fully qualified function name. For example: + // + // net/http.Error + Function string + // true if the function is exported. + IsExported bool + // Packed in which the function is defined. + Package string + // path to the source file defining the function. + // + // For standard-library, the path is relative to the src/ + // directory (e.g. net/http/server.go). + File string + // Line number, together with file, is the location where function + // is defined. + Line string + // index of the frame in the reflect_call.frames repeated field. + // + // This exists to make SQL queries easier. + Index int64 + // index of the frame across consecutive frames in the same package. + // + // This exists to make SQL queries easier. + PkgIndex int64 +} + +func (b0 Frame_builder) Build() *Frame { + m0 := &Frame{} + b, x := &b0, m0 + _, _ = b, x + x.Function = b.Function + x.IsExported = b.IsExported + x.Package = b.Package + x.File = b.File + x.Line = b.Line + x.Index = b.Index + x.PkgIndex = b.PkgIndex + return m0 +} + +type FieldAccess struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + FieldName string `protobuf:"bytes,1,opt,name=field_name,json=fieldName" json:"field_name,omitempty"` + FieldType *Type `protobuf:"bytes,2,opt,name=field_type,json=fieldType" json:"field_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldAccess) Reset() { + *x = FieldAccess{} + mi := &file_stats_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldAccess) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldAccess) ProtoMessage() {} + +func (x *FieldAccess) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *FieldAccess) GetFieldName() string { + if x != nil { + return x.FieldName + } + return "" +} + +func (x *FieldAccess) GetFieldType() *Type { + if x != nil { + return x.FieldType + } + return nil +} + +func (x *FieldAccess) SetFieldName(v string) { + x.FieldName = v +} + +func (x *FieldAccess) SetFieldType(v *Type) { + x.FieldType = v +} + +func (x *FieldAccess) HasFieldType() bool { + if x == nil { + return false + } + return x.FieldType != nil +} + +func (x *FieldAccess) ClearFieldType() { + x.FieldType = nil +} + +type FieldAccess_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + FieldName string + FieldType *Type +} + +func (b0 FieldAccess_builder) Build() *FieldAccess { + m0 := &FieldAccess{} + b, x := &b0, m0 + _, _ = b, x + x.FieldName = b.FieldName + x.FieldType = b.FieldType + return m0 +} + +type MethodCall struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Method string `protobuf:"bytes,1,opt,name=method" json:"method,omitempty"` + Type MethodCall_Type `protobuf:"varint,2,opt,name=type,enum=net.proto2.go.open2opaque.stats.MethodCall_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MethodCall) Reset() { + *x = MethodCall{} + mi := &file_stats_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MethodCall) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MethodCall) ProtoMessage() {} + +func (x *MethodCall) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MethodCall) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *MethodCall) GetType() MethodCall_Type { + if x != nil { + return x.Type + } + return MethodCall_INVALID +} + +func (x *MethodCall) SetMethod(v string) { + x.Method = v +} + +func (x *MethodCall) SetType(v MethodCall_Type) { + x.Type = v +} + +type MethodCall_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Method string + Type MethodCall_Type +} + +func (b0 MethodCall_builder) Build() *MethodCall { + m0 := &MethodCall{} + b, x := &b0, m0 + _, _ = b, x + x.Method = b.Method + x.Type = b.Type + return m0 +} + +type Constructor struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Type Constructor_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Constructor_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Constructor) Reset() { + *x = Constructor{} + mi := &file_stats_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Constructor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Constructor) ProtoMessage() {} + +func (x *Constructor) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Constructor) GetType() Constructor_Type { + if x != nil { + return x.Type + } + return Constructor_TYPE_UNSPECIFIED +} + +func (x *Constructor) SetType(v Constructor_Type) { + x.Type = v +} + +type Constructor_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Constructor_Type +} + +func (b0 Constructor_builder) Build() *Constructor { + m0 := &Constructor{} + b, x := &b0, m0 + _, _ = b, x + x.Type = b.Type + return m0 +} + +type Conversion struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The type of the conversion. For example: + // + // interface{} + // proto.Message + // unsafe.Pointer + DestTypeName string `protobuf:"bytes,1,opt,name=dest_type_name,json=destTypeName" json:"dest_type_name,omitempty"` + // Describes the called function. It is set if context==CALL_ARGUMENT. + FuncArg *FuncArg `protobuf:"bytes,3,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"` + Context Conversion_Context `protobuf:"varint,2,opt,name=context,enum=net.proto2.go.open2opaque.stats.Conversion_Context" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Conversion) Reset() { + *x = Conversion{} + mi := &file_stats_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Conversion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Conversion) ProtoMessage() {} + +func (x *Conversion) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Conversion) GetDestTypeName() string { + if x != nil { + return x.DestTypeName + } + return "" +} + +func (x *Conversion) GetFuncArg() *FuncArg { + if x != nil { + return x.FuncArg + } + return nil +} + +func (x *Conversion) GetContext() Conversion_Context { + if x != nil { + return x.Context + } + return Conversion_CONTEXT_UNSPECIFIED +} + +func (x *Conversion) SetDestTypeName(v string) { + x.DestTypeName = v +} + +func (x *Conversion) SetFuncArg(v *FuncArg) { + x.FuncArg = v +} + +func (x *Conversion) SetContext(v Conversion_Context) { + x.Context = v +} + +func (x *Conversion) HasFuncArg() bool { + if x == nil { + return false + } + return x.FuncArg != nil +} + +func (x *Conversion) ClearFuncArg() { + x.FuncArg = nil +} + +type Conversion_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The type of the conversion. For example: + // + // interface{} + // proto.Message + // unsafe.Pointer + DestTypeName string + // Describes the called function. It is set if context==CALL_ARGUMENT. + FuncArg *FuncArg + Context Conversion_Context +} + +func (b0 Conversion_builder) Build() *Conversion { + m0 := &Conversion{} + b, x := &b0, m0 + _, _ = b, x + x.DestTypeName = b.DestTypeName + x.FuncArg = b.FuncArg + x.Context = b.Context + return m0 +} + +type FuncArg struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The name of the called function. + // For example: "Clone". + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + FunctionName string `protobuf:"bytes,1,opt,name=function_name,json=functionName" json:"function_name,omitempty"` + // Full package path containing the called function. + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + PackagePath string `protobuf:"bytes,2,opt,name=package_path,json=packagePath" json:"package_path,omitempty"` + // Signature of the called function. + // For example: "func(m interface{}) interface{}". + Signature string `protobuf:"bytes,3,opt,name=signature" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FuncArg) Reset() { + *x = FuncArg{} + mi := &file_stats_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FuncArg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FuncArg) ProtoMessage() {} + +func (x *FuncArg) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *FuncArg) GetFunctionName() string { + if x != nil { + return x.FunctionName + } + return "" +} + +func (x *FuncArg) GetPackagePath() string { + if x != nil { + return x.PackagePath + } + return "" +} + +func (x *FuncArg) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +func (x *FuncArg) SetFunctionName(v string) { + x.FunctionName = v +} + +func (x *FuncArg) SetPackagePath(v string) { + x.PackagePath = v +} + +func (x *FuncArg) SetSignature(v string) { + x.Signature = v +} + +type FuncArg_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The name of the called function. + // For example: "Clone". + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + FunctionName string + // Full package path containing the called function. + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + PackagePath string + // Signature of the called function. + // For example: "func(m interface{}) interface{}". + Signature string +} + +func (b0 FuncArg_builder) Build() *FuncArg { + m0 := &FuncArg{} + b, x := &b0, m0 + _, _ = b, x + x.FunctionName = b.FunctionName + x.PackagePath = b.PackagePath + x.Signature = b.Signature + return m0 +} + +type TypeAssertion struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // The type of the expression whose type is being asserted. + SrcType *Type `protobuf:"bytes,1,opt,name=src_type,json=srcType" json:"src_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TypeAssertion) Reset() { + *x = TypeAssertion{} + mi := &file_stats_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TypeAssertion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TypeAssertion) ProtoMessage() {} + +func (x *TypeAssertion) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TypeAssertion) GetSrcType() *Type { + if x != nil { + return x.SrcType + } + return nil +} + +func (x *TypeAssertion) SetSrcType(v *Type) { + x.SrcType = v +} + +func (x *TypeAssertion) HasSrcType() bool { + if x == nil { + return false + } + return x.SrcType != nil +} + +func (x *TypeAssertion) ClearSrcType() { + x.SrcType = nil +} + +type TypeAssertion_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The type of the expression whose type is being asserted. + SrcType *Type +} + +func (b0 TypeAssertion_builder) Build() *TypeAssertion { + m0 := &TypeAssertion{} + b, x := &b0, m0 + _, _ = b, x + x.SrcType = b.SrcType + return m0 +} + +type TypeDefinition struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // new_type describes the newly defined type. + NewType *Type `protobuf:"bytes,1,opt,name=new_type,json=newType" json:"new_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TypeDefinition) Reset() { + *x = TypeDefinition{} + mi := &file_stats_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TypeDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TypeDefinition) ProtoMessage() {} + +func (x *TypeDefinition) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TypeDefinition) GetNewType() *Type { + if x != nil { + return x.NewType + } + return nil +} + +func (x *TypeDefinition) SetNewType(v *Type) { + x.NewType = v +} + +func (x *TypeDefinition) HasNewType() bool { + if x == nil { + return false + } + return x.NewType != nil +} + +func (x *TypeDefinition) ClearNewType() { + x.NewType = nil +} + +type TypeDefinition_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // new_type describes the newly defined type. + NewType *Type +} + +func (b0 TypeDefinition_builder) Build() *TypeDefinition { + m0 := &TypeDefinition{} + b, x := &b0, m0 + _, _ = b, x + x.NewType = b.NewType + return m0 +} + +type Embedding struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + FieldIndex int64 `protobuf:"varint,1,opt,name=field_index,json=fieldIndex" json:"field_index,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Embedding) Reset() { + *x = Embedding{} + mi := &file_stats_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Embedding) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Embedding) ProtoMessage() {} + +func (x *Embedding) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Embedding) GetFieldIndex() int64 { + if x != nil { + return x.FieldIndex + } + return 0 +} + +func (x *Embedding) SetFieldIndex(v int64) { + x.FieldIndex = v +} + +type Embedding_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + FieldIndex int64 +} + +func (b0 Embedding_builder) Build() *Embedding { + m0 := &Embedding{} + b, x := &b0, m0 + _, _ = b, x + x.FieldIndex = b.FieldIndex + return m0 +} + +type Source struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + File string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Source) Reset() { + *x = Source{} + mi := &file_stats_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Source) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Source) ProtoMessage() {} + +func (x *Source) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Source) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *Source) SetFile(v string) { + x.File = v +} + +type Source_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + File string +} + +func (b0 Source_builder) Build() *Source { + m0 := &Source{} + b, x := &b0, m0 + _, _ = b, x + x.File = b.File + return m0 +} + +// ShallowCopy represents a shallow copy of protocol buffers. +// +// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages +// (pointers to Go structs generated by the proto generor). +type ShallowCopy struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Type ShallowCopy_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.ShallowCopy_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ShallowCopy) Reset() { + *x = ShallowCopy{} + mi := &file_stats_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ShallowCopy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShallowCopy) ProtoMessage() {} + +func (x *ShallowCopy) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ShallowCopy) GetType() ShallowCopy_Type { + if x != nil { + return x.Type + } + return ShallowCopy_TYPE_UNSPECIFIED +} + +func (x *ShallowCopy) SetType(v ShallowCopy_Type) { + x.Type = v +} + +type ShallowCopy_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type ShallowCopy_Type +} + +func (b0 ShallowCopy_builder) Build() *ShallowCopy { + m0 := &ShallowCopy{} + b, x := &b0, m0 + _, _ = b, x + x.Type = b.Type + return m0 +} + +var File_stats_proto protoreflect.FileDescriptor + +var file_stats_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x6e, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x21, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xc9, 0x03, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, + 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x04, + 0x65, 0x78, 0x70, 0x72, 0x12, 0x36, 0x0a, 0x03, 0x75, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x52, 0x03, 0x75, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xe2, 0x01, + 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x73, 0x5f, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x46, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65, + 0x6e, 0x64, 0x22, 0x36, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x38, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f, + 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4b, 0x49, 0x50, 0x10, 0x02, 0x12, 0x08, 0x0a, + 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x03, 0x22, 0x42, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0a, 0x45, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc8, + 0x09, 0x0a, 0x03, 0x55, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, + 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x52, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x63, 0x61, + 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, + 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x4b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, + 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43, + 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, 0x75, 0x6e, 0x63, + 0x41, 0x72, 0x67, 0x12, 0x55, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, + 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x79, 0x70, + 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x0f, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, + 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x60, + 0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x13, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x12, 0x4f, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x61, 0x6c, 0x6c, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, + 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x4f, 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x70, + 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, + 0x70, 0x79, 0x22, 0xf4, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x46, 0x49, 0x45, 0x4c, + 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, + 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x43, + 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, + 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, + 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x49, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x4d, 0x42, 0x45, 0x44, 0x44, 0x49, + 0x4e, 0x47, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, + 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x08, 0x12, + 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x4c, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, + 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x50, + 0x59, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x44, 0x45, 0x50, + 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x0b, 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x52, 0x65, + 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, + 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x02, 0x66, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, + 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x02, 0x66, + 0x6e, 0x12, 0x3e, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, + 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x65, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x72, 0x0a, + 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, 0x6c, + 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x44, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, + 0x61, 0x6c, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x31, + 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, + 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46, + 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, + 0x02, 0x22, 0xa8, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x31, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, + 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4e, 0x4f, 0x4e, + 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x02, 0x12, + 0x0b, 0x0a, 0x07, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x03, 0x22, 0xea, 0x02, 0x0a, + 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x64, + 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, + 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41, + 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, + 0x0c, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, + 0x0e, 0x0a, 0x0a, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, + 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1d, 0x0a, + 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, + 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, + 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x46, + 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x07, 0x22, 0x6f, 0x0a, 0x07, 0x46, 0x75, 0x6e, + 0x63, 0x41, 0x72, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x51, 0x0a, 0x0d, 0x54, 0x79, + 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x73, + 0x72, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x73, 0x72, 0x63, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, + 0x0e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x40, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1f, + 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, + 0x1c, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xcd, 0x01, + 0x0a, 0x0b, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x45, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, + 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x03, 0x12, + 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, + 0x45, 0x52, 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x0d, + 0x0a, 0x09, 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x05, 0x2a, 0x57, 0x0a, + 0x0c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, + 0x19, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x45, 0x45, 0x4e, 0x10, + 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x59, 0x45, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x12, 0x07, 0x0a, + 0x03, 0x52, 0x45, 0x44, 0x10, 0x04, 0x42, 0x0a, 0x92, 0x03, 0x07, 0xd2, 0x3e, 0x02, 0x10, 0x02, + 0x08, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, +} + +var file_stats_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_stats_proto_goTypes = []any{ + (RewriteLevel)(0), // 0: net.proto2.go.open2opaque.stats.RewriteLevel + (Status_Type)(0), // 1: net.proto2.go.open2opaque.stats.Status.Type + (Use_Type)(0), // 2: net.proto2.go.open2opaque.stats.Use.Type + (MethodCall_Type)(0), // 3: net.proto2.go.open2opaque.stats.MethodCall.Type + (Constructor_Type)(0), // 4: net.proto2.go.open2opaque.stats.Constructor.Type + (Conversion_Context)(0), // 5: net.proto2.go.open2opaque.stats.Conversion.Context + (ShallowCopy_Type)(0), // 6: net.proto2.go.open2opaque.stats.ShallowCopy.Type + (*Entry)(nil), // 7: net.proto2.go.open2opaque.stats.Entry + (*Location)(nil), // 8: net.proto2.go.open2opaque.stats.Location + (*Position)(nil), // 9: net.proto2.go.open2opaque.stats.Position + (*Status)(nil), // 10: net.proto2.go.open2opaque.stats.Status + (*Type)(nil), // 11: net.proto2.go.open2opaque.stats.Type + (*Expression)(nil), // 12: net.proto2.go.open2opaque.stats.Expression + (*Use)(nil), // 13: net.proto2.go.open2opaque.stats.Use + (*ReflectCall)(nil), // 14: net.proto2.go.open2opaque.stats.ReflectCall + (*Frame)(nil), // 15: net.proto2.go.open2opaque.stats.Frame + (*FieldAccess)(nil), // 16: net.proto2.go.open2opaque.stats.FieldAccess + (*MethodCall)(nil), // 17: net.proto2.go.open2opaque.stats.MethodCall + (*Constructor)(nil), // 18: net.proto2.go.open2opaque.stats.Constructor + (*Conversion)(nil), // 19: net.proto2.go.open2opaque.stats.Conversion + (*FuncArg)(nil), // 20: net.proto2.go.open2opaque.stats.FuncArg + (*TypeAssertion)(nil), // 21: net.proto2.go.open2opaque.stats.TypeAssertion + (*TypeDefinition)(nil), // 22: net.proto2.go.open2opaque.stats.TypeDefinition + (*Embedding)(nil), // 23: net.proto2.go.open2opaque.stats.Embedding + (*Source)(nil), // 24: net.proto2.go.open2opaque.stats.Source + (*ShallowCopy)(nil), // 25: net.proto2.go.open2opaque.stats.ShallowCopy +} +var file_stats_proto_depIdxs = []int32{ + 10, // 0: net.proto2.go.open2opaque.stats.Entry.status:type_name -> net.proto2.go.open2opaque.stats.Status + 8, // 1: net.proto2.go.open2opaque.stats.Entry.location:type_name -> net.proto2.go.open2opaque.stats.Location + 0, // 2: net.proto2.go.open2opaque.stats.Entry.level:type_name -> net.proto2.go.open2opaque.stats.RewriteLevel + 11, // 3: net.proto2.go.open2opaque.stats.Entry.type:type_name -> net.proto2.go.open2opaque.stats.Type + 12, // 4: net.proto2.go.open2opaque.stats.Entry.expr:type_name -> net.proto2.go.open2opaque.stats.Expression + 13, // 5: net.proto2.go.open2opaque.stats.Entry.use:type_name -> net.proto2.go.open2opaque.stats.Use + 24, // 6: net.proto2.go.open2opaque.stats.Entry.source:type_name -> net.proto2.go.open2opaque.stats.Source + 9, // 7: net.proto2.go.open2opaque.stats.Location.start:type_name -> net.proto2.go.open2opaque.stats.Position + 9, // 8: net.proto2.go.open2opaque.stats.Location.end:type_name -> net.proto2.go.open2opaque.stats.Position + 1, // 9: net.proto2.go.open2opaque.stats.Status.type:type_name -> net.proto2.go.open2opaque.stats.Status.Type + 2, // 10: net.proto2.go.open2opaque.stats.Use.type:type_name -> net.proto2.go.open2opaque.stats.Use.Type + 16, // 11: net.proto2.go.open2opaque.stats.Use.direct_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess + 17, // 12: net.proto2.go.open2opaque.stats.Use.method_call:type_name -> net.proto2.go.open2opaque.stats.MethodCall + 18, // 13: net.proto2.go.open2opaque.stats.Use.constructor:type_name -> net.proto2.go.open2opaque.stats.Constructor + 19, // 14: net.proto2.go.open2opaque.stats.Use.conversion:type_name -> net.proto2.go.open2opaque.stats.Conversion + 20, // 15: net.proto2.go.open2opaque.stats.Use.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg + 21, // 16: net.proto2.go.open2opaque.stats.Use.type_assertion:type_name -> net.proto2.go.open2opaque.stats.TypeAssertion + 22, // 17: net.proto2.go.open2opaque.stats.Use.type_definition:type_name -> net.proto2.go.open2opaque.stats.TypeDefinition + 23, // 18: net.proto2.go.open2opaque.stats.Use.embedding:type_name -> net.proto2.go.open2opaque.stats.Embedding + 16, // 19: net.proto2.go.open2opaque.stats.Use.internal_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess + 14, // 20: net.proto2.go.open2opaque.stats.Use.reflect_call:type_name -> net.proto2.go.open2opaque.stats.ReflectCall + 25, // 21: net.proto2.go.open2opaque.stats.Use.shallow_copy:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy + 15, // 22: net.proto2.go.open2opaque.stats.ReflectCall.frames:type_name -> net.proto2.go.open2opaque.stats.Frame + 15, // 23: net.proto2.go.open2opaque.stats.ReflectCall.fn:type_name -> net.proto2.go.open2opaque.stats.Frame + 15, // 24: net.proto2.go.open2opaque.stats.ReflectCall.caller:type_name -> net.proto2.go.open2opaque.stats.Frame + 11, // 25: net.proto2.go.open2opaque.stats.FieldAccess.field_type:type_name -> net.proto2.go.open2opaque.stats.Type + 3, // 26: net.proto2.go.open2opaque.stats.MethodCall.type:type_name -> net.proto2.go.open2opaque.stats.MethodCall.Type + 4, // 27: net.proto2.go.open2opaque.stats.Constructor.type:type_name -> net.proto2.go.open2opaque.stats.Constructor.Type + 20, // 28: net.proto2.go.open2opaque.stats.Conversion.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg + 5, // 29: net.proto2.go.open2opaque.stats.Conversion.context:type_name -> net.proto2.go.open2opaque.stats.Conversion.Context + 11, // 30: net.proto2.go.open2opaque.stats.TypeAssertion.src_type:type_name -> net.proto2.go.open2opaque.stats.Type + 11, // 31: net.proto2.go.open2opaque.stats.TypeDefinition.new_type:type_name -> net.proto2.go.open2opaque.stats.Type + 6, // 32: net.proto2.go.open2opaque.stats.ShallowCopy.type:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy.Type + 33, // [33:33] is the sub-list for method output_type + 33, // [33:33] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name +} + +func init() { file_stats_proto_init() } +func file_stats_proto_init() { + if File_stats_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_stats_proto_rawDesc, + NumEnums: 7, + NumMessages: 19, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_stats_proto_goTypes, + DependencyIndexes: file_stats_proto_depIdxs, + EnumInfos: file_stats_proto_enumTypes, + MessageInfos: file_stats_proto_msgTypes, + }.Build() + File_stats_proto = out.File + file_stats_proto_rawDesc = nil + file_stats_proto_goTypes = nil + file_stats_proto_depIdxs = nil +}
diff --git a/internal/dashboard/stats.proto b/internal/dashboard/stats.proto new file mode 100644 index 0000000..393ac94 --- /dev/null +++ b/internal/dashboard/stats.proto
@@ -0,0 +1,318 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.stats; + +import "google/protobuf/go_features.proto"; + +option features.field_presence = IMPLICIT; + +option features.(pb.go).api_level = API_HYBRID; + +// Entry represents a single usage of a proto type. For example, a +// single field access, method call, use as a type, etc. We collect +// entries and analyze them offline to generate various statistics +// (e.g. number of direct field accesses per package) about the +// migration to opaque Go protocol buffer API. +message Entry { + // If status is not set or empty then all other fields should be set + // and describe a single usage of a protocol buffer type in Go. + // + // If status is not empty then: + // - status.error says what went wrong, and + // - location.package says which package couldn't be processed + Status status = 1; + + // Location in Go code. Always set. Location.package is always + // non-empty. + Location location = 2; + + // Rewrite level after which this entry was captured. + RewriteLevel level = 3; + + // Go type representing a protocol buffer type. + Type type = 4; + + // Go expression which leads to this entry. + Expression expr = 5; + + // Describes how a protocol buffer is used (e.g. direct field access). + Use use = 6; + + // Source of the information. For debugging purposes. + Source source = 7; +} + +// Location represents location of an expression in a Go file. +message Location { + // Full name of a Go package + string package = 1; + + // path to a Go file + string file = 2; + + // Whether the file is a generated file. + bool is_generated_file = 3; + + // Start of the expression. + Position start = 4; + + // End of the expression. + Position end = 5; +} + +// Position describes a position in a Go file. +message Position { + int64 line = 1; + int64 column = 2; +} + +// Status specifies an error that occurred. Empty error indicates +// success. +message Status { + enum Type { + UNSPECIFIED_TYPE = 0; + OK = 1; + SKIP = 2; + FAIL = 3; + } + + Type type = 1; + string error = 2; +} + +// RewriteLevel represents rewrite level after which entries are +// collected. For example, GREEN means that the tool does a green +// rewrite and analyzes the result to create entries. +enum RewriteLevel { + REWRITE_LEVEL_UNSPECIFIED = 0; + NONE = 1; + GREEN = 2; + YELLOW = 3; + RED = 4; +} + +// Type represents a Go name for a protocol buffer type. +message Type { + // The short name of the Go type representing the protocol buffer + // type. For example: "qem_go_proto.QueryEventMessage". + string short_name = 1; + + // A fully qualified name of the Go type representing the protocol + // buffer type. + string long_name = 2; +} + +// Expression describes a Go expression. +message Expression { + // go/ast expression type (e.g. "*ast.Ident"). + string type = 1; + + // go/ast expression type of the parent. + string parent_type = 2; +} + +// Use describes a use of a protocol buffer type in Go. +message Use { + enum Type { + TYPE_UNSPECIFIED = 0; + DIRECT_FIELD_ACCESS = 1; + METHOD_CALL = 2; + CONSTRUCTOR = 3; + CONVERSION = 4; + TYPE_ASSERTION = 5; + TYPE_DEFINITION = 6; + EMBEDDING = 7; + INTERNAL_FIELD_ACCESS = 8; + REFLECT_CALL = 9; + SHALLOW_COPY = 10; + BUILD_DEPENDENCY = 11; + } + + Type type = 1; + FieldAccess direct_field_access = 2; + MethodCall method_call = 3; + Constructor constructor = 4; + Conversion conversion = 5; + FuncArg func_arg = 6; + TypeAssertion type_assertion = 7; + TypeDefinition type_definition = 8; + Embedding embedding = 9; + FieldAccess internal_field_access = 10; + ReflectCall reflect_call = 11; + ShallowCopy shallow_copy = 12; +} + +// ReflectCall represents a call to the Go reflection API. +message ReflectCall { + // Information about all frames leading to the reflection call. + // + // A callstack usually looks like: + // + // reflect.Value.Field // the function triggering the log + // reflect.F1 // more functions + // ... // in the + // reflect.Fn // reflect package + // import/path.Caller // (1) fn: calls into the reflect package + // import/path.C1 // more functions + // ... // in the same package + // import/path.Cn1 // as fn + // ancestor/import.Ancestor // (2) caller: calls into fn's package + // ... // more frames + // + // The frames field has information about all frames but we also + // store a few selected frames separately to make it easier to write + // queries: + // (1) caller is the last function before the reflection package + // (2) ancestor is the last function from a different package than caller + // + // The frames field contains at least one frame (a function in the + // reflect package) but fn and caller may be unset. + repeated Frame frames = 1; + Frame fn = 2; + Frame caller = 3; +} + +// Frame represents information about a single frame in a Go +// stacktrace. +message Frame { + // Fully qualified function name. For example: + // net/http.Error + string function = 1; + + // true if the function is exported. + bool is_exported = 6; + + // Packed in which the function is defined. + string package = 2; + + // path to the source file defining the function. + // + // For standard-library, the path is relative to the src/ + // directory (e.g. net/http/server.go). + // + string file = 3; + + // Line number, together with file, is the location where function + // is defined. + string line = 4; + + // index of the frame in the reflect_call.frames repeated field. + // + // This exists to make SQL queries easier. + int64 index = 5; + + // index of the frame across consecutive frames in the same package. + // + // This exists to make SQL queries easier. + int64 pkg_index = 7; +} + +message FieldAccess { + string field_name = 1; + Type field_type = 2; +} + +message MethodCall { + string method = 1; + + enum Type { + INVALID = 0; + GET_ONEOF = 1; + GET_BUILD = 2; + } + + Type type = 2; +} + +message Constructor { + enum Type { + TYPE_UNSPECIFIED = 0; + EMPTY_LITERAL = 1; + NONEMPTY_LITERAL = 2; + BUILDER = 3; + } + + Type type = 1; +} + +message Conversion { + // The type of the conversion. For example: + // interface{} + // proto.Message + // unsafe.Pointer + string dest_type_name = 1; + + // Describes the called function. It is set if context==CALL_ARGUMENT. + FuncArg func_arg = 3; + + enum Context { + CONTEXT_UNSPECIFIED = 0; + CALL_ARGUMENT = 1; + RETURN_VALUE = 2; + ASSIGNMENT = 3; + EXPLICIT = 4; + COMPOSITE_LITERAL_ELEMENT = 5; + CHAN_SEND = 6; + FUNC_RET = 7; + } + + Context context = 2; +} + +message FuncArg { + // The name of the called function. + // For example: "Clone". + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + string function_name = 1; + + // Full package path containing the called function. + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + string package_path = 2; + + // Signature of the called function. + // For example: "func(m interface{}) interface{}". + string signature = 3; +} + +message TypeAssertion { + // The type of the expression whose type is being asserted. + Type src_type = 1; +} + +message TypeDefinition { + // new_type describes the newly defined type. + Type new_type = 1; +} + +message Embedding { + int64 field_index = 1; +} + +message Source { + string file = 1; +} + +// ShallowCopy represents a shallow copy of protocol buffers. +// +// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages +// (pointers to Go structs generated by the proto generor). +message ShallowCopy { + enum Type { + TYPE_UNSPECIFIED = 0; + ASSIGN = 1; + CALL_ARGUMENT = 2; + FUNC_RET = 3; + COMPOSITE_LITERAL_ELEMENT = 4; + CHAN_SEND = 5; + } + + Type type = 1; +}
diff --git a/internal/dashboard/stats_protoopaque.pb.go b/internal/dashboard/stats_protoopaque.pb.go new file mode 100644 index 0000000..ab0fa59 --- /dev/null +++ b/internal/dashboard/stats_protoopaque.pb.go
@@ -0,0 +1,2776 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: stats.proto + +//go:build protoopaque + +package dashboard + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/gofeaturespb" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// RewriteLevel represents rewrite level after which entries are +// collected. For example, GREEN means that the tool does a green +// rewrite and analyzes the result to create entries. +type RewriteLevel int32 + +const ( + RewriteLevel_REWRITE_LEVEL_UNSPECIFIED RewriteLevel = 0 + RewriteLevel_NONE RewriteLevel = 1 + RewriteLevel_GREEN RewriteLevel = 2 + RewriteLevel_YELLOW RewriteLevel = 3 + RewriteLevel_RED RewriteLevel = 4 +) + +// Enum value maps for RewriteLevel. +var ( + RewriteLevel_name = map[int32]string{ + 0: "REWRITE_LEVEL_UNSPECIFIED", + 1: "NONE", + 2: "GREEN", + 3: "YELLOW", + 4: "RED", + } + RewriteLevel_value = map[string]int32{ + "REWRITE_LEVEL_UNSPECIFIED": 0, + "NONE": 1, + "GREEN": 2, + "YELLOW": 3, + "RED": 4, + } +) + +func (x RewriteLevel) Enum() *RewriteLevel { + p := new(RewriteLevel) + *p = x + return p +} + +func (x RewriteLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RewriteLevel) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[0].Descriptor() +} + +func (RewriteLevel) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[0] +} + +func (x RewriteLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Status_Type int32 + +const ( + Status_UNSPECIFIED_TYPE Status_Type = 0 + Status_OK Status_Type = 1 + Status_SKIP Status_Type = 2 + Status_FAIL Status_Type = 3 +) + +// Enum value maps for Status_Type. +var ( + Status_Type_name = map[int32]string{ + 0: "UNSPECIFIED_TYPE", + 1: "OK", + 2: "SKIP", + 3: "FAIL", + } + Status_Type_value = map[string]int32{ + "UNSPECIFIED_TYPE": 0, + "OK": 1, + "SKIP": 2, + "FAIL": 3, + } +) + +func (x Status_Type) Enum() *Status_Type { + p := new(Status_Type) + *p = x + return p +} + +func (x Status_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Status_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[1].Descriptor() +} + +func (Status_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[1] +} + +func (x Status_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Use_Type int32 + +const ( + Use_TYPE_UNSPECIFIED Use_Type = 0 + Use_DIRECT_FIELD_ACCESS Use_Type = 1 + Use_METHOD_CALL Use_Type = 2 + Use_CONSTRUCTOR Use_Type = 3 + Use_CONVERSION Use_Type = 4 + Use_TYPE_ASSERTION Use_Type = 5 + Use_TYPE_DEFINITION Use_Type = 6 + Use_EMBEDDING Use_Type = 7 + Use_INTERNAL_FIELD_ACCESS Use_Type = 8 + Use_REFLECT_CALL Use_Type = 9 + Use_SHALLOW_COPY Use_Type = 10 + Use_BUILD_DEPENDENCY Use_Type = 11 +) + +// Enum value maps for Use_Type. +var ( + Use_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "DIRECT_FIELD_ACCESS", + 2: "METHOD_CALL", + 3: "CONSTRUCTOR", + 4: "CONVERSION", + 5: "TYPE_ASSERTION", + 6: "TYPE_DEFINITION", + 7: "EMBEDDING", + 8: "INTERNAL_FIELD_ACCESS", + 9: "REFLECT_CALL", + 10: "SHALLOW_COPY", + 11: "BUILD_DEPENDENCY", + } + Use_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "DIRECT_FIELD_ACCESS": 1, + "METHOD_CALL": 2, + "CONSTRUCTOR": 3, + "CONVERSION": 4, + "TYPE_ASSERTION": 5, + "TYPE_DEFINITION": 6, + "EMBEDDING": 7, + "INTERNAL_FIELD_ACCESS": 8, + "REFLECT_CALL": 9, + "SHALLOW_COPY": 10, + "BUILD_DEPENDENCY": 11, + } +) + +func (x Use_Type) Enum() *Use_Type { + p := new(Use_Type) + *p = x + return p +} + +func (x Use_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Use_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[2].Descriptor() +} + +func (Use_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[2] +} + +func (x Use_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type MethodCall_Type int32 + +const ( + MethodCall_INVALID MethodCall_Type = 0 + MethodCall_GET_ONEOF MethodCall_Type = 1 + MethodCall_GET_BUILD MethodCall_Type = 2 +) + +// Enum value maps for MethodCall_Type. +var ( + MethodCall_Type_name = map[int32]string{ + 0: "INVALID", + 1: "GET_ONEOF", + 2: "GET_BUILD", + } + MethodCall_Type_value = map[string]int32{ + "INVALID": 0, + "GET_ONEOF": 1, + "GET_BUILD": 2, + } +) + +func (x MethodCall_Type) Enum() *MethodCall_Type { + p := new(MethodCall_Type) + *p = x + return p +} + +func (x MethodCall_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MethodCall_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[3].Descriptor() +} + +func (MethodCall_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[3] +} + +func (x MethodCall_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Constructor_Type int32 + +const ( + Constructor_TYPE_UNSPECIFIED Constructor_Type = 0 + Constructor_EMPTY_LITERAL Constructor_Type = 1 + Constructor_NONEMPTY_LITERAL Constructor_Type = 2 + Constructor_BUILDER Constructor_Type = 3 +) + +// Enum value maps for Constructor_Type. +var ( + Constructor_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "EMPTY_LITERAL", + 2: "NONEMPTY_LITERAL", + 3: "BUILDER", + } + Constructor_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "EMPTY_LITERAL": 1, + "NONEMPTY_LITERAL": 2, + "BUILDER": 3, + } +) + +func (x Constructor_Type) Enum() *Constructor_Type { + p := new(Constructor_Type) + *p = x + return p +} + +func (x Constructor_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Constructor_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[4].Descriptor() +} + +func (Constructor_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[4] +} + +func (x Constructor_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type Conversion_Context int32 + +const ( + Conversion_CONTEXT_UNSPECIFIED Conversion_Context = 0 + Conversion_CALL_ARGUMENT Conversion_Context = 1 + Conversion_RETURN_VALUE Conversion_Context = 2 + Conversion_ASSIGNMENT Conversion_Context = 3 + Conversion_EXPLICIT Conversion_Context = 4 + Conversion_COMPOSITE_LITERAL_ELEMENT Conversion_Context = 5 + Conversion_CHAN_SEND Conversion_Context = 6 + Conversion_FUNC_RET Conversion_Context = 7 +) + +// Enum value maps for Conversion_Context. +var ( + Conversion_Context_name = map[int32]string{ + 0: "CONTEXT_UNSPECIFIED", + 1: "CALL_ARGUMENT", + 2: "RETURN_VALUE", + 3: "ASSIGNMENT", + 4: "EXPLICIT", + 5: "COMPOSITE_LITERAL_ELEMENT", + 6: "CHAN_SEND", + 7: "FUNC_RET", + } + Conversion_Context_value = map[string]int32{ + "CONTEXT_UNSPECIFIED": 0, + "CALL_ARGUMENT": 1, + "RETURN_VALUE": 2, + "ASSIGNMENT": 3, + "EXPLICIT": 4, + "COMPOSITE_LITERAL_ELEMENT": 5, + "CHAN_SEND": 6, + "FUNC_RET": 7, + } +) + +func (x Conversion_Context) Enum() *Conversion_Context { + p := new(Conversion_Context) + *p = x + return p +} + +func (x Conversion_Context) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Conversion_Context) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[5].Descriptor() +} + +func (Conversion_Context) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[5] +} + +func (x Conversion_Context) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type ShallowCopy_Type int32 + +const ( + ShallowCopy_TYPE_UNSPECIFIED ShallowCopy_Type = 0 + ShallowCopy_ASSIGN ShallowCopy_Type = 1 + ShallowCopy_CALL_ARGUMENT ShallowCopy_Type = 2 + ShallowCopy_FUNC_RET ShallowCopy_Type = 3 + ShallowCopy_COMPOSITE_LITERAL_ELEMENT ShallowCopy_Type = 4 + ShallowCopy_CHAN_SEND ShallowCopy_Type = 5 +) + +// Enum value maps for ShallowCopy_Type. +var ( + ShallowCopy_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "ASSIGN", + 2: "CALL_ARGUMENT", + 3: "FUNC_RET", + 4: "COMPOSITE_LITERAL_ELEMENT", + 5: "CHAN_SEND", + } + ShallowCopy_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "ASSIGN": 1, + "CALL_ARGUMENT": 2, + "FUNC_RET": 3, + "COMPOSITE_LITERAL_ELEMENT": 4, + "CHAN_SEND": 5, + } +) + +func (x ShallowCopy_Type) Enum() *ShallowCopy_Type { + p := new(ShallowCopy_Type) + *p = x + return p +} + +func (x ShallowCopy_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ShallowCopy_Type) Descriptor() protoreflect.EnumDescriptor { + return file_stats_proto_enumTypes[6].Descriptor() +} + +func (ShallowCopy_Type) Type() protoreflect.EnumType { + return &file_stats_proto_enumTypes[6] +} + +func (x ShallowCopy_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Entry represents a single usage of a proto type. For example, a +// single field access, method call, use as a type, etc. We collect +// entries and analyze them offline to generate various statistics +// (e.g. number of direct field accesses per package) about the +// migration to opaque Go protocol buffer API. +type Entry struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + xxx_hidden_Location *Location `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"` + xxx_hidden_Level RewriteLevel `protobuf:"varint,3,opt,name=level,enum=net.proto2.go.open2opaque.stats.RewriteLevel" json:"level,omitempty"` + xxx_hidden_Type *Type `protobuf:"bytes,4,opt,name=type" json:"type,omitempty"` + xxx_hidden_Expr *Expression `protobuf:"bytes,5,opt,name=expr" json:"expr,omitempty"` + xxx_hidden_Use *Use `protobuf:"bytes,6,opt,name=use" json:"use,omitempty"` + xxx_hidden_Source *Source `protobuf:"bytes,7,opt,name=source" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Entry) Reset() { + *x = Entry{} + mi := &file_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Entry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Entry) ProtoMessage() {} + +func (x *Entry) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Entry) GetStatus() *Status { + if x != nil { + return x.xxx_hidden_Status + } + return nil +} + +func (x *Entry) GetLocation() *Location { + if x != nil { + return x.xxx_hidden_Location + } + return nil +} + +func (x *Entry) GetLevel() RewriteLevel { + if x != nil { + return x.xxx_hidden_Level + } + return RewriteLevel_REWRITE_LEVEL_UNSPECIFIED +} + +func (x *Entry) GetType() *Type { + if x != nil { + return x.xxx_hidden_Type + } + return nil +} + +func (x *Entry) GetExpr() *Expression { + if x != nil { + return x.xxx_hidden_Expr + } + return nil +} + +func (x *Entry) GetUse() *Use { + if x != nil { + return x.xxx_hidden_Use + } + return nil +} + +func (x *Entry) GetSource() *Source { + if x != nil { + return x.xxx_hidden_Source + } + return nil +} + +func (x *Entry) SetStatus(v *Status) { + x.xxx_hidden_Status = v +} + +func (x *Entry) SetLocation(v *Location) { + x.xxx_hidden_Location = v +} + +func (x *Entry) SetLevel(v RewriteLevel) { + x.xxx_hidden_Level = v +} + +func (x *Entry) SetType(v *Type) { + x.xxx_hidden_Type = v +} + +func (x *Entry) SetExpr(v *Expression) { + x.xxx_hidden_Expr = v +} + +func (x *Entry) SetUse(v *Use) { + x.xxx_hidden_Use = v +} + +func (x *Entry) SetSource(v *Source) { + x.xxx_hidden_Source = v +} + +func (x *Entry) HasStatus() bool { + if x == nil { + return false + } + return x.xxx_hidden_Status != nil +} + +func (x *Entry) HasLocation() bool { + if x == nil { + return false + } + return x.xxx_hidden_Location != nil +} + +func (x *Entry) HasType() bool { + if x == nil { + return false + } + return x.xxx_hidden_Type != nil +} + +func (x *Entry) HasExpr() bool { + if x == nil { + return false + } + return x.xxx_hidden_Expr != nil +} + +func (x *Entry) HasUse() bool { + if x == nil { + return false + } + return x.xxx_hidden_Use != nil +} + +func (x *Entry) HasSource() bool { + if x == nil { + return false + } + return x.xxx_hidden_Source != nil +} + +func (x *Entry) ClearStatus() { + x.xxx_hidden_Status = nil +} + +func (x *Entry) ClearLocation() { + x.xxx_hidden_Location = nil +} + +func (x *Entry) ClearType() { + x.xxx_hidden_Type = nil +} + +func (x *Entry) ClearExpr() { + x.xxx_hidden_Expr = nil +} + +func (x *Entry) ClearUse() { + x.xxx_hidden_Use = nil +} + +func (x *Entry) ClearSource() { + x.xxx_hidden_Source = nil +} + +type Entry_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // If status is not set or empty then all other fields should be set + // and describe a single usage of a protocol buffer type in Go. + // + // If status is not empty then: + // - status.error says what went wrong, and + // - location.package says which package couldn't be processed + Status *Status + // Location in Go code. Always set. Location.package is always + // non-empty. + Location *Location + // Rewrite level after which this entry was captured. + Level RewriteLevel + // Go type representing a protocol buffer type. + Type *Type + // Go expression which leads to this entry. + Expr *Expression + // Describes how a protocol buffer is used (e.g. direct field access). + Use *Use + // Source of the information. For debugging purposes. + Source *Source +} + +func (b0 Entry_builder) Build() *Entry { + m0 := &Entry{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Status = b.Status + x.xxx_hidden_Location = b.Location + x.xxx_hidden_Level = b.Level + x.xxx_hidden_Type = b.Type + x.xxx_hidden_Expr = b.Expr + x.xxx_hidden_Use = b.Use + x.xxx_hidden_Source = b.Source + return m0 +} + +// Location represents location of an expression in a Go file. +type Location struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Package string `protobuf:"bytes,1,opt,name=package" json:"package,omitempty"` + xxx_hidden_File string `protobuf:"bytes,2,opt,name=file" json:"file,omitempty"` + xxx_hidden_IsGeneratedFile bool `protobuf:"varint,3,opt,name=is_generated_file,json=isGeneratedFile" json:"is_generated_file,omitempty"` + xxx_hidden_Start *Position `protobuf:"bytes,4,opt,name=start" json:"start,omitempty"` + xxx_hidden_End *Position `protobuf:"bytes,5,opt,name=end" json:"end,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Location) Reset() { + *x = Location{} + mi := &file_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Location) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Location) ProtoMessage() {} + +func (x *Location) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Location) GetPackage() string { + if x != nil { + return x.xxx_hidden_Package + } + return "" +} + +func (x *Location) GetFile() string { + if x != nil { + return x.xxx_hidden_File + } + return "" +} + +func (x *Location) GetIsGeneratedFile() bool { + if x != nil { + return x.xxx_hidden_IsGeneratedFile + } + return false +} + +func (x *Location) GetStart() *Position { + if x != nil { + return x.xxx_hidden_Start + } + return nil +} + +func (x *Location) GetEnd() *Position { + if x != nil { + return x.xxx_hidden_End + } + return nil +} + +func (x *Location) SetPackage(v string) { + x.xxx_hidden_Package = v +} + +func (x *Location) SetFile(v string) { + x.xxx_hidden_File = v +} + +func (x *Location) SetIsGeneratedFile(v bool) { + x.xxx_hidden_IsGeneratedFile = v +} + +func (x *Location) SetStart(v *Position) { + x.xxx_hidden_Start = v +} + +func (x *Location) SetEnd(v *Position) { + x.xxx_hidden_End = v +} + +func (x *Location) HasStart() bool { + if x == nil { + return false + } + return x.xxx_hidden_Start != nil +} + +func (x *Location) HasEnd() bool { + if x == nil { + return false + } + return x.xxx_hidden_End != nil +} + +func (x *Location) ClearStart() { + x.xxx_hidden_Start = nil +} + +func (x *Location) ClearEnd() { + x.xxx_hidden_End = nil +} + +type Location_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Full name of a Go package + Package string + // path to a Go file + File string + // Whether the file is a generated file. + IsGeneratedFile bool + // Start of the expression. + Start *Position + // End of the expression. + End *Position +} + +func (b0 Location_builder) Build() *Location { + m0 := &Location{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Package = b.Package + x.xxx_hidden_File = b.File + x.xxx_hidden_IsGeneratedFile = b.IsGeneratedFile + x.xxx_hidden_Start = b.Start + x.xxx_hidden_End = b.End + return m0 +} + +// Position describes a position in a Go file. +type Position struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Line int64 `protobuf:"varint,1,opt,name=line" json:"line,omitempty"` + xxx_hidden_Column int64 `protobuf:"varint,2,opt,name=column" json:"column,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Position) Reset() { + *x = Position{} + mi := &file_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Position) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Position) ProtoMessage() {} + +func (x *Position) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Position) GetLine() int64 { + if x != nil { + return x.xxx_hidden_Line + } + return 0 +} + +func (x *Position) GetColumn() int64 { + if x != nil { + return x.xxx_hidden_Column + } + return 0 +} + +func (x *Position) SetLine(v int64) { + x.xxx_hidden_Line = v +} + +func (x *Position) SetColumn(v int64) { + x.xxx_hidden_Column = v +} + +type Position_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Line int64 + Column int64 +} + +func (b0 Position_builder) Build() *Position { + m0 := &Position{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Line = b.Line + x.xxx_hidden_Column = b.Column + return m0 +} + +// Status specifies an error that occurred. Empty error indicates +// success. +type Status struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Type Status_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Status_Type" json:"type,omitempty"` + xxx_hidden_Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Status) Reset() { + *x = Status{} + mi := &file_stats_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Status) ProtoMessage() {} + +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Status) GetType() Status_Type { + if x != nil { + return x.xxx_hidden_Type + } + return Status_UNSPECIFIED_TYPE +} + +func (x *Status) GetError() string { + if x != nil { + return x.xxx_hidden_Error + } + return "" +} + +func (x *Status) SetType(v Status_Type) { + x.xxx_hidden_Type = v +} + +func (x *Status) SetError(v string) { + x.xxx_hidden_Error = v +} + +type Status_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Status_Type + Error string +} + +func (b0 Status_builder) Build() *Status { + m0 := &Status{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Type = b.Type + x.xxx_hidden_Error = b.Error + return m0 +} + +// Type represents a Go name for a protocol buffer type. +type Type struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_ShortName string `protobuf:"bytes,1,opt,name=short_name,json=shortName" json:"short_name,omitempty"` + xxx_hidden_LongName string `protobuf:"bytes,2,opt,name=long_name,json=longName" json:"long_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Type) Reset() { + *x = Type{} + mi := &file_stats_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Type) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Type) ProtoMessage() {} + +func (x *Type) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Type) GetShortName() string { + if x != nil { + return x.xxx_hidden_ShortName + } + return "" +} + +func (x *Type) GetLongName() string { + if x != nil { + return x.xxx_hidden_LongName + } + return "" +} + +func (x *Type) SetShortName(v string) { + x.xxx_hidden_ShortName = v +} + +func (x *Type) SetLongName(v string) { + x.xxx_hidden_LongName = v +} + +type Type_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The short name of the Go type representing the protocol buffer + // type. For example: "qem_go_proto.QueryEventMessage". + ShortName string + // A fully qualified name of the Go type representing the protocol + // buffer type. + LongName string +} + +func (b0 Type_builder) Build() *Type { + m0 := &Type{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_ShortName = b.ShortName + x.xxx_hidden_LongName = b.LongName + return m0 +} + +// Expression describes a Go expression. +type Expression struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Type string `protobuf:"bytes,1,opt,name=type" json:"type,omitempty"` + xxx_hidden_ParentType string `protobuf:"bytes,2,opt,name=parent_type,json=parentType" json:"parent_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Expression) Reset() { + *x = Expression{} + mi := &file_stats_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Expression) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Expression) ProtoMessage() {} + +func (x *Expression) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Expression) GetType() string { + if x != nil { + return x.xxx_hidden_Type + } + return "" +} + +func (x *Expression) GetParentType() string { + if x != nil { + return x.xxx_hidden_ParentType + } + return "" +} + +func (x *Expression) SetType(v string) { + x.xxx_hidden_Type = v +} + +func (x *Expression) SetParentType(v string) { + x.xxx_hidden_ParentType = v +} + +type Expression_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // go/ast expression type (e.g. "*ast.Ident"). + Type string + // go/ast expression type of the parent. + ParentType string +} + +func (b0 Expression_builder) Build() *Expression { + m0 := &Expression{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Type = b.Type + x.xxx_hidden_ParentType = b.ParentType + return m0 +} + +// Use describes a use of a protocol buffer type in Go. +type Use struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Type Use_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Use_Type" json:"type,omitempty"` + xxx_hidden_DirectFieldAccess *FieldAccess `protobuf:"bytes,2,opt,name=direct_field_access,json=directFieldAccess" json:"direct_field_access,omitempty"` + xxx_hidden_MethodCall *MethodCall `protobuf:"bytes,3,opt,name=method_call,json=methodCall" json:"method_call,omitempty"` + xxx_hidden_Constructor *Constructor `protobuf:"bytes,4,opt,name=constructor" json:"constructor,omitempty"` + xxx_hidden_Conversion *Conversion `protobuf:"bytes,5,opt,name=conversion" json:"conversion,omitempty"` + xxx_hidden_FuncArg *FuncArg `protobuf:"bytes,6,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"` + xxx_hidden_TypeAssertion *TypeAssertion `protobuf:"bytes,7,opt,name=type_assertion,json=typeAssertion" json:"type_assertion,omitempty"` + xxx_hidden_TypeDefinition *TypeDefinition `protobuf:"bytes,8,opt,name=type_definition,json=typeDefinition" json:"type_definition,omitempty"` + xxx_hidden_Embedding *Embedding `protobuf:"bytes,9,opt,name=embedding" json:"embedding,omitempty"` + xxx_hidden_InternalFieldAccess *FieldAccess `protobuf:"bytes,10,opt,name=internal_field_access,json=internalFieldAccess" json:"internal_field_access,omitempty"` + xxx_hidden_ReflectCall *ReflectCall `protobuf:"bytes,11,opt,name=reflect_call,json=reflectCall" json:"reflect_call,omitempty"` + xxx_hidden_ShallowCopy *ShallowCopy `protobuf:"bytes,12,opt,name=shallow_copy,json=shallowCopy" json:"shallow_copy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Use) Reset() { + *x = Use{} + mi := &file_stats_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Use) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Use) ProtoMessage() {} + +func (x *Use) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Use) GetType() Use_Type { + if x != nil { + return x.xxx_hidden_Type + } + return Use_TYPE_UNSPECIFIED +} + +func (x *Use) GetDirectFieldAccess() *FieldAccess { + if x != nil { + return x.xxx_hidden_DirectFieldAccess + } + return nil +} + +func (x *Use) GetMethodCall() *MethodCall { + if x != nil { + return x.xxx_hidden_MethodCall + } + return nil +} + +func (x *Use) GetConstructor() *Constructor { + if x != nil { + return x.xxx_hidden_Constructor + } + return nil +} + +func (x *Use) GetConversion() *Conversion { + if x != nil { + return x.xxx_hidden_Conversion + } + return nil +} + +func (x *Use) GetFuncArg() *FuncArg { + if x != nil { + return x.xxx_hidden_FuncArg + } + return nil +} + +func (x *Use) GetTypeAssertion() *TypeAssertion { + if x != nil { + return x.xxx_hidden_TypeAssertion + } + return nil +} + +func (x *Use) GetTypeDefinition() *TypeDefinition { + if x != nil { + return x.xxx_hidden_TypeDefinition + } + return nil +} + +func (x *Use) GetEmbedding() *Embedding { + if x != nil { + return x.xxx_hidden_Embedding + } + return nil +} + +func (x *Use) GetInternalFieldAccess() *FieldAccess { + if x != nil { + return x.xxx_hidden_InternalFieldAccess + } + return nil +} + +func (x *Use) GetReflectCall() *ReflectCall { + if x != nil { + return x.xxx_hidden_ReflectCall + } + return nil +} + +func (x *Use) GetShallowCopy() *ShallowCopy { + if x != nil { + return x.xxx_hidden_ShallowCopy + } + return nil +} + +func (x *Use) SetType(v Use_Type) { + x.xxx_hidden_Type = v +} + +func (x *Use) SetDirectFieldAccess(v *FieldAccess) { + x.xxx_hidden_DirectFieldAccess = v +} + +func (x *Use) SetMethodCall(v *MethodCall) { + x.xxx_hidden_MethodCall = v +} + +func (x *Use) SetConstructor(v *Constructor) { + x.xxx_hidden_Constructor = v +} + +func (x *Use) SetConversion(v *Conversion) { + x.xxx_hidden_Conversion = v +} + +func (x *Use) SetFuncArg(v *FuncArg) { + x.xxx_hidden_FuncArg = v +} + +func (x *Use) SetTypeAssertion(v *TypeAssertion) { + x.xxx_hidden_TypeAssertion = v +} + +func (x *Use) SetTypeDefinition(v *TypeDefinition) { + x.xxx_hidden_TypeDefinition = v +} + +func (x *Use) SetEmbedding(v *Embedding) { + x.xxx_hidden_Embedding = v +} + +func (x *Use) SetInternalFieldAccess(v *FieldAccess) { + x.xxx_hidden_InternalFieldAccess = v +} + +func (x *Use) SetReflectCall(v *ReflectCall) { + x.xxx_hidden_ReflectCall = v +} + +func (x *Use) SetShallowCopy(v *ShallowCopy) { + x.xxx_hidden_ShallowCopy = v +} + +func (x *Use) HasDirectFieldAccess() bool { + if x == nil { + return false + } + return x.xxx_hidden_DirectFieldAccess != nil +} + +func (x *Use) HasMethodCall() bool { + if x == nil { + return false + } + return x.xxx_hidden_MethodCall != nil +} + +func (x *Use) HasConstructor() bool { + if x == nil { + return false + } + return x.xxx_hidden_Constructor != nil +} + +func (x *Use) HasConversion() bool { + if x == nil { + return false + } + return x.xxx_hidden_Conversion != nil +} + +func (x *Use) HasFuncArg() bool { + if x == nil { + return false + } + return x.xxx_hidden_FuncArg != nil +} + +func (x *Use) HasTypeAssertion() bool { + if x == nil { + return false + } + return x.xxx_hidden_TypeAssertion != nil +} + +func (x *Use) HasTypeDefinition() bool { + if x == nil { + return false + } + return x.xxx_hidden_TypeDefinition != nil +} + +func (x *Use) HasEmbedding() bool { + if x == nil { + return false + } + return x.xxx_hidden_Embedding != nil +} + +func (x *Use) HasInternalFieldAccess() bool { + if x == nil { + return false + } + return x.xxx_hidden_InternalFieldAccess != nil +} + +func (x *Use) HasReflectCall() bool { + if x == nil { + return false + } + return x.xxx_hidden_ReflectCall != nil +} + +func (x *Use) HasShallowCopy() bool { + if x == nil { + return false + } + return x.xxx_hidden_ShallowCopy != nil +} + +func (x *Use) ClearDirectFieldAccess() { + x.xxx_hidden_DirectFieldAccess = nil +} + +func (x *Use) ClearMethodCall() { + x.xxx_hidden_MethodCall = nil +} + +func (x *Use) ClearConstructor() { + x.xxx_hidden_Constructor = nil +} + +func (x *Use) ClearConversion() { + x.xxx_hidden_Conversion = nil +} + +func (x *Use) ClearFuncArg() { + x.xxx_hidden_FuncArg = nil +} + +func (x *Use) ClearTypeAssertion() { + x.xxx_hidden_TypeAssertion = nil +} + +func (x *Use) ClearTypeDefinition() { + x.xxx_hidden_TypeDefinition = nil +} + +func (x *Use) ClearEmbedding() { + x.xxx_hidden_Embedding = nil +} + +func (x *Use) ClearInternalFieldAccess() { + x.xxx_hidden_InternalFieldAccess = nil +} + +func (x *Use) ClearReflectCall() { + x.xxx_hidden_ReflectCall = nil +} + +func (x *Use) ClearShallowCopy() { + x.xxx_hidden_ShallowCopy = nil +} + +type Use_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Use_Type + DirectFieldAccess *FieldAccess + MethodCall *MethodCall + Constructor *Constructor + Conversion *Conversion + FuncArg *FuncArg + TypeAssertion *TypeAssertion + TypeDefinition *TypeDefinition + Embedding *Embedding + InternalFieldAccess *FieldAccess + ReflectCall *ReflectCall + ShallowCopy *ShallowCopy +} + +func (b0 Use_builder) Build() *Use { + m0 := &Use{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Type = b.Type + x.xxx_hidden_DirectFieldAccess = b.DirectFieldAccess + x.xxx_hidden_MethodCall = b.MethodCall + x.xxx_hidden_Constructor = b.Constructor + x.xxx_hidden_Conversion = b.Conversion + x.xxx_hidden_FuncArg = b.FuncArg + x.xxx_hidden_TypeAssertion = b.TypeAssertion + x.xxx_hidden_TypeDefinition = b.TypeDefinition + x.xxx_hidden_Embedding = b.Embedding + x.xxx_hidden_InternalFieldAccess = b.InternalFieldAccess + x.xxx_hidden_ReflectCall = b.ReflectCall + x.xxx_hidden_ShallowCopy = b.ShallowCopy + return m0 +} + +// ReflectCall represents a call to the Go reflection API. +type ReflectCall struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Frames *[]*Frame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"` + xxx_hidden_Fn *Frame `protobuf:"bytes,2,opt,name=fn" json:"fn,omitempty"` + xxx_hidden_Caller *Frame `protobuf:"bytes,3,opt,name=caller" json:"caller,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReflectCall) Reset() { + *x = ReflectCall{} + mi := &file_stats_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReflectCall) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReflectCall) ProtoMessage() {} + +func (x *ReflectCall) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ReflectCall) GetFrames() []*Frame { + if x != nil { + if x.xxx_hidden_Frames != nil { + return *x.xxx_hidden_Frames + } + } + return nil +} + +func (x *ReflectCall) GetFn() *Frame { + if x != nil { + return x.xxx_hidden_Fn + } + return nil +} + +func (x *ReflectCall) GetCaller() *Frame { + if x != nil { + return x.xxx_hidden_Caller + } + return nil +} + +func (x *ReflectCall) SetFrames(v []*Frame) { + x.xxx_hidden_Frames = &v +} + +func (x *ReflectCall) SetFn(v *Frame) { + x.xxx_hidden_Fn = v +} + +func (x *ReflectCall) SetCaller(v *Frame) { + x.xxx_hidden_Caller = v +} + +func (x *ReflectCall) HasFn() bool { + if x == nil { + return false + } + return x.xxx_hidden_Fn != nil +} + +func (x *ReflectCall) HasCaller() bool { + if x == nil { + return false + } + return x.xxx_hidden_Caller != nil +} + +func (x *ReflectCall) ClearFn() { + x.xxx_hidden_Fn = nil +} + +func (x *ReflectCall) ClearCaller() { + x.xxx_hidden_Caller = nil +} + +type ReflectCall_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Information about all frames leading to the reflection call. + // + // A callstack usually looks like: + // + // reflect.Value.Field // the function triggering the log + // reflect.F1 // more functions + // ... // in the + // reflect.Fn // reflect package + // import/path.Caller // (1) fn: calls into the reflect package + // import/path.C1 // more functions + // ... // in the same package + // import/path.Cn1 // as fn + // ancestor/import.Ancestor // (2) caller: calls into fn's package + // ... // more frames + // + // The frames field has information about all frames but we also + // store a few selected frames separately to make it easier to write + // queries: + // + // (1) caller is the last function before the reflection package + // (2) ancestor is the last function from a different package than caller + // + // The frames field contains at least one frame (a function in the + // reflect package) but fn and caller may be unset. + Frames []*Frame + Fn *Frame + Caller *Frame +} + +func (b0 ReflectCall_builder) Build() *ReflectCall { + m0 := &ReflectCall{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Frames = &b.Frames + x.xxx_hidden_Fn = b.Fn + x.xxx_hidden_Caller = b.Caller + return m0 +} + +// Frame represents information about a single frame in a Go +// stacktrace. +type Frame struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Function string `protobuf:"bytes,1,opt,name=function" json:"function,omitempty"` + xxx_hidden_IsExported bool `protobuf:"varint,6,opt,name=is_exported,json=isExported" json:"is_exported,omitempty"` + xxx_hidden_Package string `protobuf:"bytes,2,opt,name=package" json:"package,omitempty"` + xxx_hidden_File string `protobuf:"bytes,3,opt,name=file" json:"file,omitempty"` + xxx_hidden_Line string `protobuf:"bytes,4,opt,name=line" json:"line,omitempty"` + xxx_hidden_Index int64 `protobuf:"varint,5,opt,name=index" json:"index,omitempty"` + xxx_hidden_PkgIndex int64 `protobuf:"varint,7,opt,name=pkg_index,json=pkgIndex" json:"pkg_index,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Frame) Reset() { + *x = Frame{} + mi := &file_stats_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Frame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Frame) ProtoMessage() {} + +func (x *Frame) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Frame) GetFunction() string { + if x != nil { + return x.xxx_hidden_Function + } + return "" +} + +func (x *Frame) GetIsExported() bool { + if x != nil { + return x.xxx_hidden_IsExported + } + return false +} + +func (x *Frame) GetPackage() string { + if x != nil { + return x.xxx_hidden_Package + } + return "" +} + +func (x *Frame) GetFile() string { + if x != nil { + return x.xxx_hidden_File + } + return "" +} + +func (x *Frame) GetLine() string { + if x != nil { + return x.xxx_hidden_Line + } + return "" +} + +func (x *Frame) GetIndex() int64 { + if x != nil { + return x.xxx_hidden_Index + } + return 0 +} + +func (x *Frame) GetPkgIndex() int64 { + if x != nil { + return x.xxx_hidden_PkgIndex + } + return 0 +} + +func (x *Frame) SetFunction(v string) { + x.xxx_hidden_Function = v +} + +func (x *Frame) SetIsExported(v bool) { + x.xxx_hidden_IsExported = v +} + +func (x *Frame) SetPackage(v string) { + x.xxx_hidden_Package = v +} + +func (x *Frame) SetFile(v string) { + x.xxx_hidden_File = v +} + +func (x *Frame) SetLine(v string) { + x.xxx_hidden_Line = v +} + +func (x *Frame) SetIndex(v int64) { + x.xxx_hidden_Index = v +} + +func (x *Frame) SetPkgIndex(v int64) { + x.xxx_hidden_PkgIndex = v +} + +type Frame_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fully qualified function name. For example: + // + // net/http.Error + Function string + // true if the function is exported. + IsExported bool + // Packed in which the function is defined. + Package string + // path to the source file defining the function. + // + // For standard-library, the path is relative to the src/ + // directory (e.g. net/http/server.go). + File string + // Line number, together with file, is the location where function + // is defined. + Line string + // index of the frame in the reflect_call.frames repeated field. + // + // This exists to make SQL queries easier. + Index int64 + // index of the frame across consecutive frames in the same package. + // + // This exists to make SQL queries easier. + PkgIndex int64 +} + +func (b0 Frame_builder) Build() *Frame { + m0 := &Frame{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Function = b.Function + x.xxx_hidden_IsExported = b.IsExported + x.xxx_hidden_Package = b.Package + x.xxx_hidden_File = b.File + x.xxx_hidden_Line = b.Line + x.xxx_hidden_Index = b.Index + x.xxx_hidden_PkgIndex = b.PkgIndex + return m0 +} + +type FieldAccess struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_FieldName string `protobuf:"bytes,1,opt,name=field_name,json=fieldName" json:"field_name,omitempty"` + xxx_hidden_FieldType *Type `protobuf:"bytes,2,opt,name=field_type,json=fieldType" json:"field_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldAccess) Reset() { + *x = FieldAccess{} + mi := &file_stats_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldAccess) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldAccess) ProtoMessage() {} + +func (x *FieldAccess) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *FieldAccess) GetFieldName() string { + if x != nil { + return x.xxx_hidden_FieldName + } + return "" +} + +func (x *FieldAccess) GetFieldType() *Type { + if x != nil { + return x.xxx_hidden_FieldType + } + return nil +} + +func (x *FieldAccess) SetFieldName(v string) { + x.xxx_hidden_FieldName = v +} + +func (x *FieldAccess) SetFieldType(v *Type) { + x.xxx_hidden_FieldType = v +} + +func (x *FieldAccess) HasFieldType() bool { + if x == nil { + return false + } + return x.xxx_hidden_FieldType != nil +} + +func (x *FieldAccess) ClearFieldType() { + x.xxx_hidden_FieldType = nil +} + +type FieldAccess_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + FieldName string + FieldType *Type +} + +func (b0 FieldAccess_builder) Build() *FieldAccess { + m0 := &FieldAccess{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_FieldName = b.FieldName + x.xxx_hidden_FieldType = b.FieldType + return m0 +} + +type MethodCall struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Method string `protobuf:"bytes,1,opt,name=method" json:"method,omitempty"` + xxx_hidden_Type MethodCall_Type `protobuf:"varint,2,opt,name=type,enum=net.proto2.go.open2opaque.stats.MethodCall_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MethodCall) Reset() { + *x = MethodCall{} + mi := &file_stats_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MethodCall) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MethodCall) ProtoMessage() {} + +func (x *MethodCall) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *MethodCall) GetMethod() string { + if x != nil { + return x.xxx_hidden_Method + } + return "" +} + +func (x *MethodCall) GetType() MethodCall_Type { + if x != nil { + return x.xxx_hidden_Type + } + return MethodCall_INVALID +} + +func (x *MethodCall) SetMethod(v string) { + x.xxx_hidden_Method = v +} + +func (x *MethodCall) SetType(v MethodCall_Type) { + x.xxx_hidden_Type = v +} + +type MethodCall_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Method string + Type MethodCall_Type +} + +func (b0 MethodCall_builder) Build() *MethodCall { + m0 := &MethodCall{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Method = b.Method + x.xxx_hidden_Type = b.Type + return m0 +} + +type Constructor struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Type Constructor_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.Constructor_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Constructor) Reset() { + *x = Constructor{} + mi := &file_stats_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Constructor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Constructor) ProtoMessage() {} + +func (x *Constructor) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Constructor) GetType() Constructor_Type { + if x != nil { + return x.xxx_hidden_Type + } + return Constructor_TYPE_UNSPECIFIED +} + +func (x *Constructor) SetType(v Constructor_Type) { + x.xxx_hidden_Type = v +} + +type Constructor_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type Constructor_Type +} + +func (b0 Constructor_builder) Build() *Constructor { + m0 := &Constructor{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Type = b.Type + return m0 +} + +type Conversion struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_DestTypeName string `protobuf:"bytes,1,opt,name=dest_type_name,json=destTypeName" json:"dest_type_name,omitempty"` + xxx_hidden_FuncArg *FuncArg `protobuf:"bytes,3,opt,name=func_arg,json=funcArg" json:"func_arg,omitempty"` + xxx_hidden_Context Conversion_Context `protobuf:"varint,2,opt,name=context,enum=net.proto2.go.open2opaque.stats.Conversion_Context" json:"context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Conversion) Reset() { + *x = Conversion{} + mi := &file_stats_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Conversion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Conversion) ProtoMessage() {} + +func (x *Conversion) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Conversion) GetDestTypeName() string { + if x != nil { + return x.xxx_hidden_DestTypeName + } + return "" +} + +func (x *Conversion) GetFuncArg() *FuncArg { + if x != nil { + return x.xxx_hidden_FuncArg + } + return nil +} + +func (x *Conversion) GetContext() Conversion_Context { + if x != nil { + return x.xxx_hidden_Context + } + return Conversion_CONTEXT_UNSPECIFIED +} + +func (x *Conversion) SetDestTypeName(v string) { + x.xxx_hidden_DestTypeName = v +} + +func (x *Conversion) SetFuncArg(v *FuncArg) { + x.xxx_hidden_FuncArg = v +} + +func (x *Conversion) SetContext(v Conversion_Context) { + x.xxx_hidden_Context = v +} + +func (x *Conversion) HasFuncArg() bool { + if x == nil { + return false + } + return x.xxx_hidden_FuncArg != nil +} + +func (x *Conversion) ClearFuncArg() { + x.xxx_hidden_FuncArg = nil +} + +type Conversion_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The type of the conversion. For example: + // + // interface{} + // proto.Message + // unsafe.Pointer + DestTypeName string + // Describes the called function. It is set if context==CALL_ARGUMENT. + FuncArg *FuncArg + Context Conversion_Context +} + +func (b0 Conversion_builder) Build() *Conversion { + m0 := &Conversion{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_DestTypeName = b.DestTypeName + x.xxx_hidden_FuncArg = b.FuncArg + x.xxx_hidden_Context = b.Context + return m0 +} + +type FuncArg struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_FunctionName string `protobuf:"bytes,1,opt,name=function_name,json=functionName" json:"function_name,omitempty"` + xxx_hidden_PackagePath string `protobuf:"bytes,2,opt,name=package_path,json=packagePath" json:"package_path,omitempty"` + xxx_hidden_Signature string `protobuf:"bytes,3,opt,name=signature" json:"signature,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FuncArg) Reset() { + *x = FuncArg{} + mi := &file_stats_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FuncArg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FuncArg) ProtoMessage() {} + +func (x *FuncArg) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *FuncArg) GetFunctionName() string { + if x != nil { + return x.xxx_hidden_FunctionName + } + return "" +} + +func (x *FuncArg) GetPackagePath() string { + if x != nil { + return x.xxx_hidden_PackagePath + } + return "" +} + +func (x *FuncArg) GetSignature() string { + if x != nil { + return x.xxx_hidden_Signature + } + return "" +} + +func (x *FuncArg) SetFunctionName(v string) { + x.xxx_hidden_FunctionName = v +} + +func (x *FuncArg) SetPackagePath(v string) { + x.xxx_hidden_PackagePath = v +} + +func (x *FuncArg) SetSignature(v string) { + x.xxx_hidden_Signature = v +} + +type FuncArg_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The name of the called function. + // For example: "Clone". + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + FunctionName string + // Full package path containing the called function. + // + // An empty string means that analysis couldn't determine which + // function is called (this could happen for indirect calls). + PackagePath string + // Signature of the called function. + // For example: "func(m interface{}) interface{}". + Signature string +} + +func (b0 FuncArg_builder) Build() *FuncArg { + m0 := &FuncArg{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_FunctionName = b.FunctionName + x.xxx_hidden_PackagePath = b.PackagePath + x.xxx_hidden_Signature = b.Signature + return m0 +} + +type TypeAssertion struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_SrcType *Type `protobuf:"bytes,1,opt,name=src_type,json=srcType" json:"src_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TypeAssertion) Reset() { + *x = TypeAssertion{} + mi := &file_stats_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TypeAssertion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TypeAssertion) ProtoMessage() {} + +func (x *TypeAssertion) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TypeAssertion) GetSrcType() *Type { + if x != nil { + return x.xxx_hidden_SrcType + } + return nil +} + +func (x *TypeAssertion) SetSrcType(v *Type) { + x.xxx_hidden_SrcType = v +} + +func (x *TypeAssertion) HasSrcType() bool { + if x == nil { + return false + } + return x.xxx_hidden_SrcType != nil +} + +func (x *TypeAssertion) ClearSrcType() { + x.xxx_hidden_SrcType = nil +} + +type TypeAssertion_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // The type of the expression whose type is being asserted. + SrcType *Type +} + +func (b0 TypeAssertion_builder) Build() *TypeAssertion { + m0 := &TypeAssertion{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_SrcType = b.SrcType + return m0 +} + +type TypeDefinition struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_NewType *Type `protobuf:"bytes,1,opt,name=new_type,json=newType" json:"new_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TypeDefinition) Reset() { + *x = TypeDefinition{} + mi := &file_stats_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TypeDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TypeDefinition) ProtoMessage() {} + +func (x *TypeDefinition) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *TypeDefinition) GetNewType() *Type { + if x != nil { + return x.xxx_hidden_NewType + } + return nil +} + +func (x *TypeDefinition) SetNewType(v *Type) { + x.xxx_hidden_NewType = v +} + +func (x *TypeDefinition) HasNewType() bool { + if x == nil { + return false + } + return x.xxx_hidden_NewType != nil +} + +func (x *TypeDefinition) ClearNewType() { + x.xxx_hidden_NewType = nil +} + +type TypeDefinition_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // new_type describes the newly defined type. + NewType *Type +} + +func (b0 TypeDefinition_builder) Build() *TypeDefinition { + m0 := &TypeDefinition{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_NewType = b.NewType + return m0 +} + +type Embedding struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_FieldIndex int64 `protobuf:"varint,1,opt,name=field_index,json=fieldIndex" json:"field_index,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Embedding) Reset() { + *x = Embedding{} + mi := &file_stats_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Embedding) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Embedding) ProtoMessage() {} + +func (x *Embedding) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Embedding) GetFieldIndex() int64 { + if x != nil { + return x.xxx_hidden_FieldIndex + } + return 0 +} + +func (x *Embedding) SetFieldIndex(v int64) { + x.xxx_hidden_FieldIndex = v +} + +type Embedding_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + FieldIndex int64 +} + +func (b0 Embedding_builder) Build() *Embedding { + m0 := &Embedding{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_FieldIndex = b.FieldIndex + return m0 +} + +type Source struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_File string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Source) Reset() { + *x = Source{} + mi := &file_stats_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Source) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Source) ProtoMessage() {} + +func (x *Source) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *Source) GetFile() string { + if x != nil { + return x.xxx_hidden_File + } + return "" +} + +func (x *Source) SetFile(v string) { + x.xxx_hidden_File = v +} + +type Source_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + File string +} + +func (b0 Source_builder) Build() *Source { + m0 := &Source{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_File = b.File + return m0 +} + +// ShallowCopy represents a shallow copy of protocol buffers. +// +// For example: "*m2 = *m1" for m1, m2 being protocol buffer messages +// (pointers to Go structs generated by the proto generor). +type ShallowCopy struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Type ShallowCopy_Type `protobuf:"varint,1,opt,name=type,enum=net.proto2.go.open2opaque.stats.ShallowCopy_Type" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ShallowCopy) Reset() { + *x = ShallowCopy{} + mi := &file_stats_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ShallowCopy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShallowCopy) ProtoMessage() {} + +func (x *ShallowCopy) ProtoReflect() protoreflect.Message { + mi := &file_stats_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ShallowCopy) GetType() ShallowCopy_Type { + if x != nil { + return x.xxx_hidden_Type + } + return ShallowCopy_TYPE_UNSPECIFIED +} + +func (x *ShallowCopy) SetType(v ShallowCopy_Type) { + x.xxx_hidden_Type = v +} + +type ShallowCopy_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Type ShallowCopy_Type +} + +func (b0 ShallowCopy_builder) Build() *ShallowCopy { + m0 := &ShallowCopy{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Type = b.Type + return m0 +} + +var File_stats_proto protoreflect.FileDescriptor + +var file_stats_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x6e, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x1a, 0x21, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xc9, 0x03, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x3f, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, + 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, + 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x65, 0x78, 0x70, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x04, + 0x65, 0x78, 0x70, 0x72, 0x12, 0x36, 0x0a, 0x03, 0x75, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x52, 0x03, 0x75, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xe2, 0x01, + 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x73, 0x5f, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x46, 0x69, 0x6c, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x65, + 0x6e, 0x64, 0x22, 0x36, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x06, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x38, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x4f, + 0x4b, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x4b, 0x49, 0x50, 0x10, 0x02, 0x12, 0x08, 0x0a, + 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x03, 0x22, 0x42, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x41, 0x0a, 0x0a, 0x45, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc8, + 0x09, 0x0a, 0x03, 0x55, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, + 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x52, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x12, 0x4c, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x63, 0x61, + 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, + 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x4b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, + 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43, + 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, 0x75, 0x6e, 0x63, + 0x41, 0x72, 0x67, 0x12, 0x55, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, + 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x74, 0x79, 0x70, + 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x0f, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, + 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x60, + 0x0a, 0x15, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x13, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x12, 0x4f, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x63, 0x61, 0x6c, 0x6c, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, + 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x0b, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x4f, 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x70, + 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, + 0x70, 0x79, 0x22, 0xf4, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x46, 0x49, 0x45, 0x4c, + 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, + 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x43, + 0x4f, 0x4e, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, + 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x05, + 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x49, 0x54, + 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x4d, 0x42, 0x45, 0x44, 0x44, 0x49, + 0x4e, 0x47, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, + 0x5f, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x08, 0x12, + 0x10, 0x0a, 0x0c, 0x52, 0x45, 0x46, 0x4c, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, + 0x09, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x5f, 0x43, 0x4f, 0x50, + 0x59, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x44, 0x45, 0x50, + 0x45, 0x4e, 0x44, 0x45, 0x4e, 0x43, 0x59, 0x10, 0x0b, 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x52, 0x65, + 0x66, 0x6c, 0x65, 0x63, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x3e, 0x0a, 0x06, 0x66, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, + 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x02, 0x66, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, + 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x02, 0x66, + 0x6e, 0x12, 0x3e, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, + 0x72, 0x22, 0xb9, 0x01, 0x0a, 0x05, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x65, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, + 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x72, 0x0a, + 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x61, 0x6c, 0x6c, + 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x44, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, + 0x61, 0x6c, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x31, + 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, + 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x45, 0x4f, 0x46, + 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x54, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x10, + 0x02, 0x22, 0xa8, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x31, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, + 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x4e, 0x4f, 0x4e, + 0x45, 0x4d, 0x50, 0x54, 0x59, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, 0x41, 0x4c, 0x10, 0x02, 0x12, + 0x0b, 0x0a, 0x07, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x45, 0x52, 0x10, 0x03, 0x22, 0xea, 0x02, 0x0a, + 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x64, + 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x43, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x5f, 0x61, 0x72, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x52, 0x07, 0x66, + 0x75, 0x6e, 0x63, 0x41, 0x72, 0x67, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x41, + 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, + 0x0c, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, + 0x0e, 0x0a, 0x0a, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x03, 0x12, + 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1d, 0x0a, + 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, 0x45, 0x52, + 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, + 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x46, + 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x07, 0x22, 0x6f, 0x0a, 0x07, 0x46, 0x75, 0x6e, + 0x63, 0x41, 0x72, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x51, 0x0a, 0x0d, 0x54, 0x79, + 0x70, 0x65, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x73, + 0x72, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x73, 0x72, 0x63, 0x54, 0x79, 0x70, 0x65, 0x22, 0x52, 0x0a, + 0x0e, 0x54, 0x79, 0x70, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x40, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1f, + 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, + 0x1c, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0xcd, 0x01, + 0x0a, 0x0b, 0x53, 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x45, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x53, 0x68, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x77, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x53, 0x53, 0x49, 0x47, 0x4e, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x41, 0x52, 0x47, 0x55, 0x4d, 0x45, 0x4e, 0x54, 0x10, + 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x55, 0x4e, 0x43, 0x5f, 0x52, 0x45, 0x54, 0x10, 0x03, 0x12, + 0x1d, 0x0a, 0x19, 0x43, 0x4f, 0x4d, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x54, + 0x45, 0x52, 0x41, 0x4c, 0x5f, 0x45, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x0d, + 0x0a, 0x09, 0x43, 0x48, 0x41, 0x4e, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x05, 0x2a, 0x57, 0x0a, + 0x0c, 0x52, 0x65, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, + 0x19, 0x52, 0x45, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x45, 0x45, 0x4e, 0x10, + 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x59, 0x45, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x12, 0x07, 0x0a, + 0x03, 0x52, 0x45, 0x44, 0x10, 0x04, 0x42, 0x0a, 0x92, 0x03, 0x07, 0xd2, 0x3e, 0x02, 0x10, 0x02, + 0x08, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, +} + +var file_stats_proto_enumTypes = make([]protoimpl.EnumInfo, 7) +var file_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_stats_proto_goTypes = []any{ + (RewriteLevel)(0), // 0: net.proto2.go.open2opaque.stats.RewriteLevel + (Status_Type)(0), // 1: net.proto2.go.open2opaque.stats.Status.Type + (Use_Type)(0), // 2: net.proto2.go.open2opaque.stats.Use.Type + (MethodCall_Type)(0), // 3: net.proto2.go.open2opaque.stats.MethodCall.Type + (Constructor_Type)(0), // 4: net.proto2.go.open2opaque.stats.Constructor.Type + (Conversion_Context)(0), // 5: net.proto2.go.open2opaque.stats.Conversion.Context + (ShallowCopy_Type)(0), // 6: net.proto2.go.open2opaque.stats.ShallowCopy.Type + (*Entry)(nil), // 7: net.proto2.go.open2opaque.stats.Entry + (*Location)(nil), // 8: net.proto2.go.open2opaque.stats.Location + (*Position)(nil), // 9: net.proto2.go.open2opaque.stats.Position + (*Status)(nil), // 10: net.proto2.go.open2opaque.stats.Status + (*Type)(nil), // 11: net.proto2.go.open2opaque.stats.Type + (*Expression)(nil), // 12: net.proto2.go.open2opaque.stats.Expression + (*Use)(nil), // 13: net.proto2.go.open2opaque.stats.Use + (*ReflectCall)(nil), // 14: net.proto2.go.open2opaque.stats.ReflectCall + (*Frame)(nil), // 15: net.proto2.go.open2opaque.stats.Frame + (*FieldAccess)(nil), // 16: net.proto2.go.open2opaque.stats.FieldAccess + (*MethodCall)(nil), // 17: net.proto2.go.open2opaque.stats.MethodCall + (*Constructor)(nil), // 18: net.proto2.go.open2opaque.stats.Constructor + (*Conversion)(nil), // 19: net.proto2.go.open2opaque.stats.Conversion + (*FuncArg)(nil), // 20: net.proto2.go.open2opaque.stats.FuncArg + (*TypeAssertion)(nil), // 21: net.proto2.go.open2opaque.stats.TypeAssertion + (*TypeDefinition)(nil), // 22: net.proto2.go.open2opaque.stats.TypeDefinition + (*Embedding)(nil), // 23: net.proto2.go.open2opaque.stats.Embedding + (*Source)(nil), // 24: net.proto2.go.open2opaque.stats.Source + (*ShallowCopy)(nil), // 25: net.proto2.go.open2opaque.stats.ShallowCopy +} +var file_stats_proto_depIdxs = []int32{ + 10, // 0: net.proto2.go.open2opaque.stats.Entry.status:type_name -> net.proto2.go.open2opaque.stats.Status + 8, // 1: net.proto2.go.open2opaque.stats.Entry.location:type_name -> net.proto2.go.open2opaque.stats.Location + 0, // 2: net.proto2.go.open2opaque.stats.Entry.level:type_name -> net.proto2.go.open2opaque.stats.RewriteLevel + 11, // 3: net.proto2.go.open2opaque.stats.Entry.type:type_name -> net.proto2.go.open2opaque.stats.Type + 12, // 4: net.proto2.go.open2opaque.stats.Entry.expr:type_name -> net.proto2.go.open2opaque.stats.Expression + 13, // 5: net.proto2.go.open2opaque.stats.Entry.use:type_name -> net.proto2.go.open2opaque.stats.Use + 24, // 6: net.proto2.go.open2opaque.stats.Entry.source:type_name -> net.proto2.go.open2opaque.stats.Source + 9, // 7: net.proto2.go.open2opaque.stats.Location.start:type_name -> net.proto2.go.open2opaque.stats.Position + 9, // 8: net.proto2.go.open2opaque.stats.Location.end:type_name -> net.proto2.go.open2opaque.stats.Position + 1, // 9: net.proto2.go.open2opaque.stats.Status.type:type_name -> net.proto2.go.open2opaque.stats.Status.Type + 2, // 10: net.proto2.go.open2opaque.stats.Use.type:type_name -> net.proto2.go.open2opaque.stats.Use.Type + 16, // 11: net.proto2.go.open2opaque.stats.Use.direct_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess + 17, // 12: net.proto2.go.open2opaque.stats.Use.method_call:type_name -> net.proto2.go.open2opaque.stats.MethodCall + 18, // 13: net.proto2.go.open2opaque.stats.Use.constructor:type_name -> net.proto2.go.open2opaque.stats.Constructor + 19, // 14: net.proto2.go.open2opaque.stats.Use.conversion:type_name -> net.proto2.go.open2opaque.stats.Conversion + 20, // 15: net.proto2.go.open2opaque.stats.Use.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg + 21, // 16: net.proto2.go.open2opaque.stats.Use.type_assertion:type_name -> net.proto2.go.open2opaque.stats.TypeAssertion + 22, // 17: net.proto2.go.open2opaque.stats.Use.type_definition:type_name -> net.proto2.go.open2opaque.stats.TypeDefinition + 23, // 18: net.proto2.go.open2opaque.stats.Use.embedding:type_name -> net.proto2.go.open2opaque.stats.Embedding + 16, // 19: net.proto2.go.open2opaque.stats.Use.internal_field_access:type_name -> net.proto2.go.open2opaque.stats.FieldAccess + 14, // 20: net.proto2.go.open2opaque.stats.Use.reflect_call:type_name -> net.proto2.go.open2opaque.stats.ReflectCall + 25, // 21: net.proto2.go.open2opaque.stats.Use.shallow_copy:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy + 15, // 22: net.proto2.go.open2opaque.stats.ReflectCall.frames:type_name -> net.proto2.go.open2opaque.stats.Frame + 15, // 23: net.proto2.go.open2opaque.stats.ReflectCall.fn:type_name -> net.proto2.go.open2opaque.stats.Frame + 15, // 24: net.proto2.go.open2opaque.stats.ReflectCall.caller:type_name -> net.proto2.go.open2opaque.stats.Frame + 11, // 25: net.proto2.go.open2opaque.stats.FieldAccess.field_type:type_name -> net.proto2.go.open2opaque.stats.Type + 3, // 26: net.proto2.go.open2opaque.stats.MethodCall.type:type_name -> net.proto2.go.open2opaque.stats.MethodCall.Type + 4, // 27: net.proto2.go.open2opaque.stats.Constructor.type:type_name -> net.proto2.go.open2opaque.stats.Constructor.Type + 20, // 28: net.proto2.go.open2opaque.stats.Conversion.func_arg:type_name -> net.proto2.go.open2opaque.stats.FuncArg + 5, // 29: net.proto2.go.open2opaque.stats.Conversion.context:type_name -> net.proto2.go.open2opaque.stats.Conversion.Context + 11, // 30: net.proto2.go.open2opaque.stats.TypeAssertion.src_type:type_name -> net.proto2.go.open2opaque.stats.Type + 11, // 31: net.proto2.go.open2opaque.stats.TypeDefinition.new_type:type_name -> net.proto2.go.open2opaque.stats.Type + 6, // 32: net.proto2.go.open2opaque.stats.ShallowCopy.type:type_name -> net.proto2.go.open2opaque.stats.ShallowCopy.Type + 33, // [33:33] is the sub-list for method output_type + 33, // [33:33] is the sub-list for method input_type + 33, // [33:33] is the sub-list for extension type_name + 33, // [33:33] is the sub-list for extension extendee + 0, // [0:33] is the sub-list for field type_name +} + +func init() { file_stats_proto_init() } +func file_stats_proto_init() { + if File_stats_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_stats_proto_rawDesc, + NumEnums: 7, + NumMessages: 19, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_stats_proto_goTypes, + DependencyIndexes: file_stats_proto_depIdxs, + EnumInfos: file_stats_proto_enumTypes, + MessageInfos: file_stats_proto_msgTypes, + }.Build() + File_stats_proto = out.File + file_stats_proto_rawDesc = nil + file_stats_proto_goTypes = nil + file_stats_proto_depIdxs = nil +}
diff --git a/internal/fix/appendprotos.go b/internal/fix/appendprotos.go new file mode 100644 index 0000000..ab416f6 --- /dev/null +++ b/internal/fix/appendprotos.go
@@ -0,0 +1,85 @@ +// 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 fix + +import ( + "go/token" + + "github.com/dave/dst" +) + +func appendProtosPre(c *cursor) bool { + // Handle the following as a single unit: + // + // m.R = append(m.R, msg, ...) + // => + // m.SetR(append(m.GetR(), msg, ...)) + // + // + // Note that this rewrite can be handled as two independent rewrites: + // + // Step 1 + // m.R = append(m.R, msg, ...) + // => + // m.R = append(m.GetR(), msg, ...) + // + // Step2 + // m.R = append(m.GetR(), msg, ...) + // => + // m.SetR(append(m.GetR(), msg, ...)) + // + // So this function may seem redundant at first. However, in practice, + // those multi-step rewrites are not very reliable in presence of + // failures and it's not uncommon to end up with partially updated + // pattern or a misplaced comment. Hence, we handle the pattern as a + // whole. + a, ok := c.Node().(*dst.AssignStmt) + if !ok { + c.Logf("ignoring %T (looking for AssignStmt)", c.Node()) + return true + } + if a.Tok != token.ASSIGN || len(a.Lhs) != 1 || len(a.Rhs) != 1 { + c.Logf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", a.Tok) + return true + } + if len(a.Lhs) != 1 || len(a.Rhs) != 1 { + c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(a.Lhs), len(a.Rhs)) + return true + } + + appendCall, ok := a.Rhs[0].(*dst.CallExpr) + if !ok { + c.Logf("ignoring AssignStmt with rhs %T (looking for CallExpr)", a.Rhs[0]) + return true + } + if len(appendCall.Args) == 0 { + c.Logf("ignoring rhs CallExpr with %d args (looking for 1)", len(appendCall.Args)) + return true + } + ident, ok := appendCall.Fun.(*dst.Ident) + if !ok { + c.Logf("ignoring rhs CallExpr with Fun %T (looking for Ident)", appendCall.Fun) + return true + } + if obj := c.objectOf(ident); obj.Name() != "append" || obj.Pkg() != nil { + c.Logf("ignoring rhs CallExpr with Obj %v (looking for append())", obj) + return true + } + + lsel, ok := c.trackedProtoFieldSelector(a.Lhs[0]) + if !ok { + c.Logf("ignoring: lhs is not a proto field selector") + return true + } + rsel, ok := c.trackedProtoFieldSelector(appendCall.Args[0]) + if !ok { + c.Logf("ignoring: rhs append() arg is not a proto field selector") + return true + } + + appendCall.Args[0] = sel2call(c, "Get", rsel, nil, *rsel.Decorations()) + c.Replace(c.expr2stmt(sel2call(c, "Set", lsel, appendCall, *c.Node().Decorations()), lsel)) + return true +}
diff --git a/internal/fix/appendprotos_test.go b/internal/fix/appendprotos_test.go new file mode 100644 index 0000000..a6b63d1 --- /dev/null +++ b/internal/fix/appendprotos_test.go
@@ -0,0 +1,46 @@ +// 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 fix + +import ( + "testing" +) + +func TestAppend(t *testing.T) { + tt := test{ + in: ` +m2.Ms = append(m2.Ms, nil) +m2.Is = append(m2.Is, 1) +m3.Ms = append(m3.Ms, nil) +m3.Is = append(m3.Is, 1) + +m3.Is = append(m3.Is, 1, 2, 3) +m3.Is = append(m3.Is, append(m3.Is, 1)...) + +// append with a comment +m3.Is = append(m3.Is, 1) + +m3.Is = append(m3.Is) +`, + want: map[Level]string{ + Green: ` +m2.SetMs(append(m2.GetMs(), nil)) +m2.SetIs(append(m2.GetIs(), 1)) +m3.SetMs(append(m3.GetMs(), nil)) +m3.SetIs(append(m3.GetIs(), 1)) + +m3.SetIs(append(m3.GetIs(), 1, 2, 3)) +m3.SetIs(append(m3.GetIs(), append(m3.GetIs(), 1)...)) + +// append with a comment +m3.SetIs(append(m3.GetIs(), 1)) + +m3.SetIs(append(m3.GetIs())) +`, + }, + } + + runTableTest(t, tt) +}
diff --git a/internal/fix/assign.go b/internal/fix/assign.go new file mode 100644 index 0000000..9c766e3 --- /dev/null +++ b/internal/fix/assign.go
@@ -0,0 +1,1004 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" +) + +// assignPre rewrites assignments. This function is executed by traversing the tree in preorder. +// Assignments with operations are handled by assignOpPre. +func assignPre(c *cursor) bool { + stmt, ok := c.Node().(*dst.AssignStmt) + if !ok { + c.Logf("ignoring %T (looking for AssignStmt)", c.Node()) + return true + } + if stmt.Tok != token.ASSIGN { + c.Logf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", stmt.Tok) + return true + } + + // Not handled: shallow copy support. + + if len(stmt.Lhs) != len(stmt.Rhs) { + c.Logf("ignoring AssignStmt with len(lhs)=%d != len(rhs)=%d", len(stmt.Lhs), len(stmt.Rhs)) + // Not handled: assignments where len(lhs) != len(rhs): + // - calls + // - chan ops + // - map Access + // - type assertions + return true + } + + // Handle the most common case: single assignment. + if len(stmt.Lhs) == 1 { + c.Logf("len(lhs) = 1") + lhs, rhs := stmt.Lhs[0], stmt.Rhs[0] + + // *m.F = v => m.SetF(v) + if star, ok := lhs.(*dst.StarExpr); ok { + c.Logf("lhs is a StarExpr") + sel, ok := c.trackedProtoFieldSelector(star.X) + if !ok { + c.Logf("ignoring: lhs is not a proto field selector") + return true + } + c.Replace(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel)) + c.Logf("rewriting AssignStmt") + return true + } + + // Handle "m.F = v" + field, ok := c.protoFieldSelector(lhs) + if !ok { + c.Logf("ignoring: lhs is not a proto field selector") + return true + } + // Check if either side is a proto field selector in -types_to_update + // and update the whole assignment. + _, lhsOk := c.trackedProtoFieldSelector(lhs) + _, rhsOk := c.trackedProtoFieldSelector(rhs) + if !lhsOk && !rhsOk { + c.Logf("ignoring: neither lhs nor rhs are (tracked) proto field selectors") + return true + } + c.Logf("attempting to rewrite...") + if a, ok := rewriteFieldAssign(c, field, rhs, *stmt.Decorations()); ok { + c.Logf("...success") + c.Replace(a) + } else { + c.Logf("...failure") + } + + return true + } + + // Rewriting multi-assignment may change order of evaluation. Hence this is a yellow rewrite. + if c.lvl.le(Green) { + return true + } + + if err := assignmentIsSwap(c, stmt); err == nil && !c.lvl.ge(Yellow) { + // Swaps are yellow rewrites because getPost only handles them in the yellow level. + return true + } else if err != nil { + c.Logf("%s", err.Error()) + } + + // Multi-assignment in simple statements is handled by assignPost. It requires updating + // grandparent (calling parent.InsertBefore) of the rewritten node and the dstutil.Cursor API + // doesn't provide a way to do that. Here, we only update statements in blocks because we can + // call c.InsertBefore. + + if _, ok := c.Cursor.Parent().(*dst.BlockStmt); !ok { + c.Logf("ignoring: multi-assignment outside of BlockStmt (handled by assignPost)") + return true + } + + // Don't change the multi-assignment structure if there are no proto-related + // rewrites in there. + var usesProtos bool + for _, lhs := range stmt.Lhs { + if star, ok := lhs.(*dst.StarExpr); ok { + if _, ok := c.trackedProtoFieldSelector(star.X); ok { + usesProtos = true + break + } + } + if _, ok := c.trackedProtoFieldSelector(lhs); ok { + usesProtos = true + break + } + } + if !usesProtos { + c.Logf("ignoring AssignStmt without protos") + return true + } + + var decs dst.NodeDecs + lastIdx := len(stmt.Lhs) - 1 + for i, lhs := range stmt.Lhs { + switch i { + case 0: + decs = dst.NodeDecs{ + Before: stmt.Decorations().Before, + Start: stmt.Decorations().Start, + } + case lastIdx: + decs = dst.NodeDecs{ + After: stmt.Decorations().After, + End: stmt.Decorations().End, + } + default: + decs = dst.NodeDecs{} + } + rhs := stmt.Rhs[i] + + // rewrite "*m.F = v" + if star, ok := lhs.(*dst.StarExpr); ok { + if sel, ok := c.trackedProtoFieldSelector(star.X); ok { + c.Logf("rewriting %v = %v", sel.Sel.Name, rhs) + c.InsertBefore(c.expr2stmt(sel2call(c, "Set", sel, rhs, *stmt.Decorations()), sel)) + } else { + c.Logf("ignoring: lhs is not a proto field selector") + } + continue + } + + // rewrite "m.F = v" + if field, ok := c.trackedProtoFieldSelector(lhs); ok { + c.Logf("lhs %d is a proto field selector", i) + if a, ok := rewriteFieldAssign(c, field, rhs, decs); ok { + c.Logf("rewriting %v = %v", field.Sel.Name, rhs) + c.InsertBefore(a) + continue + } + } + as := &dst.AssignStmt{ + Lhs: []dst.Expr{lhs}, + Tok: token.ASSIGN, + Rhs: []dst.Expr{rhs}, + } + as.Decs.NodeDecs = decs + c.Logf("rewriting %v = %v", lhs, rhs) + c.InsertBefore(as) + } + c.Delete() + return true +} + +// assignPost rewrites multi-assignments in simple statements. This function is executed by +// traversing the tree in postorder. +func assignPost(c *cursor) bool { + // Splitting multi-assignments is a yellow rewrite because it changes order of evaluation. + if c.lvl.le(Green) { + return true + } + + // If either expression a or b is a proto direct field access, rewrite: + // + // if a, b = f(), g(); cond { // Similar for init statements in "for" and "switch". + // => + // a = f() + // b = g() + // if cond { + // + // and apply single-assignment rewrite rules for individual assign statements. + var initStmt *dst.AssignStmt + n := c.Node() + switch n := n.(type) { + case *dst.IfStmt: + if isMultiProtoAssign(c, n.Init) { + initStmt = n.Init.(*dst.AssignStmt) + n.Init = nil + } + case *dst.ForStmt: + if isMultiProtoAssign(c, n.Init) { + initStmt = n.Init.(*dst.AssignStmt) + n.Init = nil + } + case *dst.SwitchStmt: + if isMultiProtoAssign(c, n.Init) { + initStmt = n.Init.(*dst.AssignStmt) + n.Init = nil + } + case *dst.TypeSwitchStmt: + if isMultiProtoAssign(c, n.Init) { + initStmt = n.Init.(*dst.AssignStmt) + n.Init = nil + } + } + if initStmt == nil { + return true + } + for i, lhs := range initStmt.Lhs { + rhs := initStmt.Rhs[i] + if a, ok := rewriteFieldAssign(c, lhs, rhs, dst.NodeDecs{}); ok { + c.InsertBefore(a) + continue + } + c.InsertBefore(&dst.AssignStmt{ + Lhs: []dst.Expr{lhs}, + Tok: token.ASSIGN, + Rhs: []dst.Expr{rhs}, + }) + } + c.numUnsafeRewritesByReason[EvalOrderChange]++ + return true +} + +// assignOpPre rewrites assignment operations (x op= y). +// This function is executed by traversing the tree in preorder. +func assignOpPre(c *cursor) bool { + stmt, ok := c.Node().(*dst.AssignStmt) + if !ok { + return true + } + if stmt.Tok == token.ASSIGN || stmt.Tok == token.DEFINE { + return false + } + + // Assignment operations must have exactly one lhs and one rhs value, + // see https://go.dev/ref/spec#Assignment_statements. + lhs, rhs := stmt.Lhs[0], stmt.Rhs[0] + + if star, ok := lhs.(*dst.StarExpr); ok { + lhs = star.X + } + sel, ok := c.trackedProtoFieldSelector(lhs) + if !ok { + return false + } + + tok := stmt.Tok + switch stmt.Tok { + case token.ADD_ASSIGN: + tok = token.ADD + case token.SUB_ASSIGN: + tok = token.SUB + case token.MUL_ASSIGN: + tok = token.MUL + case token.QUO_ASSIGN: + tok = token.QUO + case token.REM_ASSIGN: + tok = token.REM + case token.AND_ASSIGN: + tok = token.AND + case token.OR_ASSIGN: + tok = token.OR + case token.XOR_ASSIGN: + tok = token.XOR + case token.SHL_ASSIGN: + tok = token.SHL + case token.SHR_ASSIGN: + tok = token.SHR + case token.AND_NOT_ASSIGN: + tok = token.AND_NOT + default: + c.Logf("unexpected token: %v", stmt.Tok) + return false + } + binExpr := &dst.BinaryExpr{ + X: sel2call(c, "Get", sel, nil, dst.NodeDecs{}), + Op: tok, + Y: rhs, + } + c.setType(binExpr, c.typeOf(lhs)) + + startEndDec := dst.NodeDecs{ + Start: stmt.Decorations().Start, + End: stmt.Decorations().End, + } + selClone := cloneSelectorExpr(c, sel) + c.Replace(c.expr2stmt(sel2call(c, "Set", selClone, binExpr, startEndDec), selClone)) + + return false +} + +func isNeverNilSliceExpr(c *cursor, e dst.Expr) bool { + // Is this a string to byte conversion? + if ce, ok := e.(*dst.CallExpr); ok && len(ce.Args) == 1 { + if bt, ok := c.typeOf(ce.Args[0]).(*types.Basic); ok && bt.Kind() == types.String { + if at, ok := ce.Fun.(*dst.ArrayType); ok { + if id, ok := at.Elt.(*dst.Ident); ok && id.Name == "byte" { + return true + } + } + } + } + if _, ok := e.(*dst.CompositeLit); ok { + if _, ok := c.typeOf(e).(*types.Slice); ok { + return true + } + } + se, ok := e.(*dst.SliceExpr) + if !ok { + return false + } + if bl, ok := se.Low.(*dst.BasicLit); ok && bl.Value != "0" { + return true + } + if bl, ok := se.High.(*dst.BasicLit); ok && bl.Value != "0" { + return true + } + return false +} + +// rewriteFieldAssign rewrites a direct field assignment +// +// lhs = rhs where lhs is "m.F" +// +// to a form that works in the opaque proto API world. +func rewriteFieldAssign(c *cursor, lhs, rhs dst.Expr, decs dst.NodeDecs) (dst.Stmt, bool) { + lhsSel, ok := c.protoFieldSelector(lhs) + if !ok { + c.Logf("ignoring: lhs is not a proto field selector") + return nil, false + } + + // Drop parens around rhs, if any. Rhs becomes an argument to a function call. It's never + // necessary to keep it in parenthesis. One situation where rhs is a ParenExpr happens when it + // used to be a composite literal in a simple statement. For example: + // + // if _, M.F = nil, (&pb.M{}); { + if pe, ok := rhs.(*dst.ParenExpr); ok { + rhs = pe.X + } + + // m.F = proto.{String, Int, ...}(V) => m.SetF(V) + if arg, ok := protoHelperCall(c, rhs); ok { + c.Logf("rewriting proto helper call") + return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true + } + + // m.F = pb.EnumValue.Enum() => m.SetF(pb.EnumValue) + if enumVal, ok := enumHelperCall(c, rhs); ok { + c.Logf("rewriting Enum() call") + + if t := c.typeOfOrNil(enumVal); t != nil { + if pt, ok := t.(*types.Pointer); ok { + enumVal = &dst.StarExpr{X: enumVal} + c.setType(enumVal, pt.Elem()) + } + } + + return c.expr2stmt(sel2call(c, "Set", lhsSel, enumVal, decs), lhsSel), true + } + + // m.F = new(MsgType) => m.SetF(new(MsgType)) + // m.F = new(BasicType) => m.SetF(ZeroValueOf(BasicType)) + // m.F = new(EnumType) => m.SetF(EnumType(0)) + if arg, ok := newConstructorCall(c, rhs); ok { + c.Logf("rewriting constructor") + return c.expr2stmt(sel2call(c, "Set", lhsSel, arg, decs), lhsSel), true + } + + // m.F = nil => m.ClearF() + if ident, ok := rhs.(*dst.Ident); ok && ident.Name == "nil" { + c.Logf("rewriting nil assignment...") + if c.useClearOrHas(lhsSel) { + c.Logf("...with Clear()") + return c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, decs), lhsSel), true + } + c.Logf("...with Set()") + return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true + } + + // This condition is intentionally placed after the ClearF condition just + // above so that we do not need to handle the nil assignment case. + // + // m.F = ptrToEnum + // => + // if ptrToEnum != nil { + // m1.SetF(*ptrToEnum) + // } else { + // m1.ClearF() + // } + if t := c.typeOfOrNil(lhsSel); t != nil && isPtr(t) { + et := t.Underlying().(*types.Pointer).Elem() + _, fieldCopy := c.trackedProtoFieldSelector(rhs) + c.Logf("isEnum(%v) = %v, fieldCopy = %v", et, isEnum(et), fieldCopy) + if !fieldCopy && isEnum(et) { + // special case for m.E = &eVal => m.SetE(eVal) + if ue, ok := rhs.(*dst.UnaryExpr); ok && ue.Op == token.AND { + stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, ue.X, decs), lhsSel) + return stmt, true + } + lhs2 := cloneSelectorExpr(c, lhsSel) + ifStmt, v := ifNonNil(c, lhsSel, rhs, decs) + ifStmt.Body.List = []dst.Stmt{ + c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2), + } + return ifStmt, true + } + } + + // m.F = []byte(v) => m.SetF([]byte(v)) + // m.F = []byte("...") => m.SetF([]byte("...")) + if isBytesConversion(c, rhs) { + c.Logf("rewriting []byte conversion") + return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true + } + + // For proto2 bytes field, rewrite statement + // m.F = Expr + // => + // if x := Expr; x != nil { + // m.SetF(x) + // } + // + // Or, if we cannot determine whether Clear() is safe to omit: + // + // if x := Expr; x != nil { + // m.SetF(x) + // } else { + // m.ClearF() + // } + // + // Can only rewrite for statements and not for expressions. + if _, ok := c.Parent().(*dst.BlockStmt); ok { + c.Logf("rewriting assignment with rhs Expr") + f := c.objectOf(lhsSel.Sel).(*types.Var) + isProto2 := !isProto3Field(c.typeOf(lhsSel.X), f.Name()) + if slice, ok := f.Type().(*types.Slice); ok && isProto2 { + if basic, ok := slice.Elem().(*types.Basic); ok && basic.Kind() == types.Uint8 { + if isNeverNilSliceExpr(c, rhs) { + stmt := c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel) + return stmt, true + } + // Duplicate LHS so that it can be used in both "then" and "else" blocks. + // It's ok, regardless of the exact details of LHS, because: + // - before this change, LHS was evaluated exactly once, and + // - after this change, LHS is still evaluated exactly once + lhsSelClone := cloneSelectorExpr(c, lhsSel) + ifStmt, v := ifNonNil(c, lhsSel, rhs, decs) + ifStmt.Body.List = []dst.Stmt{ + c.expr2stmt(sel2call(c, "Set", lhsSelClone, cloneExpr(c, v), dst.NodeDecs{}), lhsSelClone), + } + return ifStmt, true + } + } + } + + // m.Oneof = &pb.M_Oneof{K: V} => m.SetK(V) + // m.Oneof = &pb.M_Oneof{V} => m.SetK(V) + // m.Oneof = &pb.M_Oneof{} => m.SetK(ZeroValueOf(BasicType)) + // + // Where K is the field name of the sole field in M_Oneof. + if isOneof(c.typeOf(lhsSel.Sel)) { + c.Logf("rewriting oneof wrapper") + name, typ, val, oneofDecs, ok := destructureOneofWrapper(c, rhs) + if !ok { + c.Logf("ignoring: destructuring oneof wrapper failed") + if c.lvl.ge(Red) { + c.numUnsafeRewritesByReason[OneofFieldAccess]++ + addCommentAbove(c.Node(), lhsSel.X, "// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).") + } + return nil, false + } + if val == nil { + if !isScalar(typ) { + c.Logf("...failed because lhs is not a scalar type") + return nil, false + } + val = scalarTypeZeroExpr(c, typ) + } + lhsSel.Sel.Name = name + c.Logf("...success") + if oneofDecs != nil { + decs.Start = append(decs.Start, (*oneofDecs).Start...) + decs.End = append(decs.End, (*oneofDecs).End...) + } + return c.expr2stmt(sel2call(c, "Set", lhsSel, val, decs), lhsSel), true + } + + // m1.F = m2.F + // + // [proto2] + // if m2.HasF() { + // m1.SetF(m2.GetF()) + // } else { + // m1.ClearF() + // } + // (or variations based on existence of side effects when evaluating m1 or m2) + // + // [proto3] + // m1.SetF(m2.GetF()) + isProto2 := isPtrToBasic(c.underlyingTypeOf(lhsSel)) // Bytes fields don't behave like other proto2 scalars. + if rhsSel, ok := c.trackedProtoFieldSelector(rhs); ok { + c.Logf("rewriting proto field to proto field assignment") + if !isProto2 { + return c.expr2stmt(sel2call(c, "Set", lhsSel, sel2call(c, "Get", rhsSel, nil, dst.NodeDecs{}), decs), lhsSel), true + } + + // If RHS has no side effects then just evaluate it inline. However, if it + // is not known to be side-effect free then evaluate it once, in the init + // statement. + var rhs1 *dst.SelectorExpr // We need two copies of rhs. They are identical. + var initStmt dst.Stmt + if c.isSideEffectFree(rhsSel) { + rhs1 = rhsSel + } else { + v := &dst.Ident{Name: "x"} + c.setType(v, c.typeOf(rhsSel.X)) + + initStmt = &dst.AssignStmt{ + Lhs: []dst.Expr{v}, + Tok: token.DEFINE, + Rhs: []dst.Expr{rhsSel.X}, + } + + rhs1 = &dst.SelectorExpr{ + X: &dst.Ident{Name: v.Name}, + Sel: rhsSel.Sel, + } + c.setType(rhs1, c.typeOf(rhsSel)) + c.setType(rhs1.X, c.typeOf(v)) + } + rhs2 := cloneSelectorExpr(c, rhs1) + + lhs1, lhs2 := lhsSel, cloneSelectorExpr(c, lhsSel) // We need two copies of LHS. They are identical. + var elseStmt dst.Stmt + if clearNeeded(c, lhsSel) { + c.Logf("Clear() statement is needed") + elseStmt = c.expr2stmt(sel2call(c, "Clear", lhs1, nil, dst.NodeDecs{}), lhs1) + } + + // Move end-of-line comments to above the if conditional. + if len(decs.End) > 0 { + decs.Start = append(decs.Start, decs.End...) + decs.End = nil + } + var stmt dst.Stmt = c.expr2stmt(sel2call(c, "Set", lhs2, sel2call(c, "Get", rhs2, nil, dst.NodeDecs{}), dst.NodeDecs{}), lhs2) + if hasNeeded(c, rhsSel) { + stmt = &dst.IfStmt{ + Init: initStmt, + Cond: sel2call(c, "Has", rhs1, nil, dst.NodeDecs{}), + Body: &dst.BlockStmt{ + List: []dst.Stmt{ + stmt, + }, + }, + Else: elseStmt, + Decs: dst.IfStmtDecorations{NodeDecs: decs}, + } + } + return stmt, true + } + + // m.F = V => m.SetF(V) + if !isProto2 { + c.Logf("rewriting direct field access to setter") + return c.expr2stmt(sel2call(c, "Set", lhsSel, rhs, decs), lhsSel), true + } + + // m.F = &V => m.SetF(V) + if isAddr(rhs) { + c.Logf("rewriting direct field access to setter (rhs &Expr)") + return c.expr2stmt(sel2call(c, "Set", lhsSel, deref(c, rhs), decs), lhsSel), true + } + + // m.F = V => m.SetF(*V) (red rewrite: loses aliasing) + if c.lvl.ge(Red) { + c.Logf("rewriting direct field access to setter (losing pointer aliasing)") + lhs2 := cloneSelectorExpr(c, lhsSel) + ifStmt, v := ifNonNil(c, lhsSel, rhs, decs) + ifStmt.Body.List = []dst.Stmt{ + c.expr2stmt(sel2call(c, "Set", lhs2, deref(c, cloneExpr(c, v)), dst.NodeDecs{}), lhs2), + } + c.numUnsafeRewritesByReason[PointerAlias]++ + return ifStmt, true + } + + c.Logf("no applicable rewrite found") + return nil, false +} + +// clearNeeded figures out if for the specified SelectorExpr, a Clear() call +// needs to be inserted before or not. +// +// clearNeeded looks at the AST of the current scope, considering all statements +// between the initial assignment and the SelectorExpr: +// +// … // (not checked yet) +// mm2 := &mypb.Message{} // initial assignment +// mm2.SetBytes([]byte("hello world")) // checked by clearNeeded() +// … // checked by clearNeeded() +// mm2.I32 = 23 // SelectorExpr +// proto.Merge(mm2, src) // (not checked anymore) +// … +func clearNeeded(c *cursor, sel *dst.SelectorExpr) bool { + // sel is something like dst.SelectorExpr{ + // X: &dst.Ident{"mm2"}, + // Sel: &dst.Ident{"I32"}, + // } + if _, ok := sel.X.(*dst.Ident); !ok { + return true + } + + innerMost, _ := c.enclosingASTStmt(sel.X) + if innerMost == nil { + return true + } + enclosing, ok := c.typesInfo.dstMap[innerMost] + if !ok { + c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost) + return true + } + first := compositLiteralInitialer(enclosing, sel.X.(*dst.Ident).Name) + if first == nil { + // The variable we are looking for is not initialized + // unconditionally in this scope. + return true + } + firstSeen := false + lastSeen := false + usageFound := false + var visit visitorFunc + xObj := c.objectOf(sel.X.(*dst.Ident)) + selObj := c.objectOf(sel.Sel) + selName := fixConflictingNames(c.typeOf(sel.X), "Set", sel.Sel.Name) + + visit = func(n dst.Node) dst.Visitor { + if n == first { + firstSeen = true + // Don't check the children of the definition because + // it would look like a usage. + return nil + } + if !firstSeen { + // As long as we have not seen the first node we don't + // need to look at any of the statements because they + // cannot influence first. + return visit + } + if lastSeen { + return nil + } + + if as, ok := n.(*dst.AssignStmt); ok { + for _, lhs := range as.Lhs { + if lhs == sel { + lastSeen = true + // Skip recursing into children; all subsequent visit() calls + // will return immediately. + return nil + } + + // Is the field assigned to? + if usesObject(c, lhs, xObj) && usesObject(c, lhs, selObj) { + usageFound = true + return nil + } + } + } + + // Access is okay if it's not a setter for the field + // and if it is not assigned to (checked above). + if doesNotModifyField(c, n, selName) { + return nil + } + + if id, ok := n.(*dst.Ident); ok && id.Name == sel.X.(*dst.Ident).Name { + c.Logf("found non-proto-field-selector usage of %q", id.Name) + usageFound = true + } + + return visit // recurse into children + } + dst.Walk(visit, enclosing) + // Clear() calls are definitely needed when: + // + // 1. !firstSeen — we couldn’t find the declaration of <sel> in the + // innermost scope. + // + // 2. or !lastSeen — we couldn’t find the usage of <sel> (bug?) + // + // 3. or usageFound — we did find a usage that we didn’t expect. + return !firstSeen || !lastSeen || usageFound +} + +// isMultiProtoAssign returns true if stmt is a multi-assignment that assigns at least one protocol +// buffer field. Note that irregular assignments (e.g. "a,b := m[c]") are not considered to be +// multi-assignments. +func isMultiProtoAssign(c *cursor, stmt dst.Stmt) bool { + a, ok := stmt.(*dst.AssignStmt) + if !ok || len(a.Lhs) < 2 || len(a.Lhs) != len(a.Rhs) { + return false + } + for _, lhs := range a.Lhs { + if _, ok := c.trackedProtoFieldSelector(lhs); ok { + return true + } + } + return false +} + +// isBytesConversion returns true if x is an explicit conversion to a slice of +// bytes. That is, when x has the form "[]byte(...)". +func isBytesConversion(c *cursor, x dst.Expr) bool { + call, ok := x.(*dst.CallExpr) + if !ok { + return false + } + fun, ok := call.Fun.(*dst.ArrayType) + if !ok { + return false + } + ident, ok := fun.Elt.(*dst.Ident) + if !ok { + return false + } + return c.objectOf(ident) == types.Universe.Lookup("byte") +} + +// newConstructorCall returns true if x is a 'new' call. It also returns an +// argument that should be provided to a corresponding proto setter if the new +// call was to be replaced by a set call. +func newConstructorCall(c *cursor, expr dst.Expr) (dst.Expr, bool) { + call, ok := expr.(*dst.CallExpr) + if !ok || len(call.Args) != 1 { + return nil, false + } + ident, ok := call.Fun.(*dst.Ident) + if !ok { + return nil, false + } + if c.objectOf(ident) != types.Universe.Lookup("new") { + return nil, false + } + + t := c.typeOf(call.Args[0]) + if t, ok := t.(*types.Basic); ok { + return scalarTypeZeroExpr(c, t), true + } + + if _, ok := t.(*types.Named); !ok { + return nil, false + } + + // Message + if _, ok := t.Underlying().(*types.Struct); ok { + return call, true // new(M) is fine + } + + // Enum + if !isBasic(t.Underlying()) { + return nil, false + } + zero := &dst.Ident{Name: "0"} + c.setType(zero, types.Typ[types.UntypedInt]) + conv := &dst.CallExpr{ + Fun: call.Args[0], + Args: []dst.Expr{zero}, + } + c.setType(conv, t) + return conv, true +} + +// enumHelperCall returns true if expr is a enum helper call (e.g. +// "pb.MyMessage_MyEnumVal.Enum()"). If so, it also returns the enum value +// ("MyEnumVal" in the previous example). +func enumHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) { + call, ok := expr.(*dst.CallExpr) + if !ok { + return nil, false + } + sel, ok := call.Fun.(*dst.SelectorExpr) + if !ok { + return nil, false + } + if sel.Sel.Name != "Enum" { + return nil, false + } + var res dst.Expr = sel.X + // It is possible to use methods enums as if they were free functions by + // passing the receiver as first argument. We know that the Enum method + // does not have any parameters. This means if it is called with an argument + // it must be used as free function and the argument is the receiver, e.g.: + // (e.g. "pb.MyMessage_MyEnum.Enum(pb.MyMessage_MyEnumVal)"). + // + // Note: we cannot use the type system to determine whether the free function + // or method is used because these are two are the same from the type + // systems's point of view. + if len(call.Args) == 1 { + res = call.Args[0] + } + return res, true +} + +// isSelectorExprWithIdent return true if e is a simple selector expression where +// X is of type *dst.Ident. +func isSelectorExprWithIdent(e dst.Expr) bool { + rhsSel, ok := e.(*dst.SelectorExpr) + if !ok { + return false + } + if _, ok := rhsSel.X.(*dst.Ident); !ok { + return false + } + return true +} + +// ifNonNil returns a *dst.IfStmt that checks if rhs is not nil (if rhs is nil, +// Clear() is called). If needed, a temporary variable (x) is introduced to only +// evaluate rhs once. The returned *dst.Ident refers either to the temporary +// variable (if needed) or to rhs. If possible, the Clear() call will be elided. +func ifNonNil(c *cursor, lhsSel *dst.SelectorExpr, rhs dst.Expr, decs dst.NodeDecs) (*dst.IfStmt, dst.Expr) { + var elseStmt dst.Stmt + if clearNeeded(c, lhsSel) { + c.Logf("Clear() statement is needed") + elseStmt = c.expr2stmt(sel2call(c, "Clear", lhsSel, nil, dst.NodeDecs{}), lhsSel) + } + + var v dst.Expr + var initAssign dst.Stmt + if rhsIdent, ok := rhs.(*dst.Ident); ok { + // If RHS is already an identifier, we skip generating an + // initializer in the if statement (x := rhs) and just use + // the RHS directly. + v = rhsIdent + } else if isSelectorExprWithIdent(rhs) { + v = rhs + } else { + v = &dst.Ident{Name: "x"} + c.setType(v, c.typeOf(rhs)) + initAssign = &dst.AssignStmt{ + Lhs: []dst.Expr{cloneIdent(c, v.(*dst.Ident))}, + Tok: token.DEFINE, + Rhs: []dst.Expr{rhs}, + } + } + + untypedNil := &dst.Ident{Name: "nil"} + c.setType(untypedNil, types.Typ[types.UntypedNil]) + cond := &dst.BinaryExpr{ + X: v, + Op: token.NEQ, + Y: untypedNil, + } + c.setType(cond, types.Typ[types.Bool]) + + // Move end-of-line comments to above the if conditional. + if len(decs.End) > 0 { + decs.Start = append(decs.Start, decs.End...) + decs.End = nil + } + + return &dst.IfStmt{ + Init: initAssign, + Cond: cond, + Body: &dst.BlockStmt{}, + Else: elseStmt, + Decs: dst.IfStmtDecorations{NodeDecs: decs}, + }, v +} + +// doesNotModifyField returns true if n does not modify a proto field named name. +func doesNotModifyField(c *cursor, n dst.Node, name string) bool { + if selFun, sig, ok := c.protoFieldSelectorOrAccessor(n); ok { + if sig == nil || selFun.Sel.Name != "Set"+name { + // Skip recursing into children: proto field selector usages are + // okay; we could still skip the Clear() methods. + return true + } + } + return false +} + +// compositLiteralInitialer return the node that is a direct child of enclosing +// and initialized a variabled named `name` unconditionally with a composite +// literal. Returns nil if there is no such node. +func compositLiteralInitialer(enclosing dst.Node, name string) dst.Node { + for _, n := range directChildren(enclosing) { + as, ok := n.(*dst.AssignStmt) + if !ok { + continue + } + if len(as.Lhs) != len(as.Rhs) { + continue + } + for i, lhs := range as.Lhs { + if id, ok := lhs.(*dst.Ident); ok && id.Name == name { + // We consider everything but composite literal + // initialization as unknown and thus unsafe. + if _, ok := isCompositeLit(as.Rhs[i], as); ok { + return n + } + } + } + } + return nil +} + +func usesObject(c *cursor, expr dst.Expr, obj types.Object) bool { + found := false + var visit visitorFunc + visit = func(n dst.Node) dst.Visitor { + id, ok := n.(*dst.Ident) + if !ok { + return visit + } + + if c.objectOf(id) == obj { + found = true + return nil + } + return visit + } + dst.Walk(visit, expr) + return found +} + +// directChildren returns a list of dst.Stmts if the n is a node opens a scope. +func directChildren(n dst.Node) []dst.Stmt { + switch t := n.(type) { + case *dst.BlockStmt: + return t.List + case *dst.CaseClause: + return t.Body + case *dst.CommClause: + return t.Body + } + return nil +} + +func deref(c *cursor, expr dst.Expr) dst.Expr { + ue, ok := expr.(*dst.UnaryExpr) + if ok { + return ue.X + } + out := &dst.StarExpr{X: expr} + c.setType(out, c.underlyingTypeOf(expr).(*types.Pointer).Elem()) + return out +} + +func stringIn(s string, ss []string) bool { + for _, v := range ss { + if s == v { + return true + } + } + return false +} + +// protoHelperCall returns true if expr is a proto helper call (e.g. "proto.String(s)"). If so, it +// also returns argument to the helper. +func protoHelperCall(c *cursor, expr dst.Expr) (dst.Expr, bool) { + call, ok := expr.(*dst.CallExpr) + if !ok { + return nil, false + } + sel, ok := call.Fun.(*dst.SelectorExpr) + if !ok { + return nil, false + } + if sel.Sel.Name == "Int" && !isBasicLit(call.Args[0]) { + // "m.F = proto.Int(v)" => "m.SetF(int32(v))" + x := &dst.CallExpr{ + Fun: dst.NewIdent("int32"), + Args: []dst.Expr{call.Args[0]}, + } + c.setType(x, types.Universe.Lookup("int32").Type()) + c.setType(x.Fun, types.Universe.Lookup("int32").Type()) + return x, true + } + if !stringIn(sel.Sel.Name, []string{"Bool", "Float32", "Float64", "Int", "Int32", "Int64", "String", "Uint32", "Uint64"}) { + return nil, false + } + if c.objectOf(sel.Sel).Pkg().Path() != protoImport { + return nil, false + } + return call.Args[0], true +} + +func isBasicLit(x dst.Expr) bool { + _, ok := x.(*dst.BasicLit) + return ok +}
diff --git a/internal/fix/assign_test.go b/internal/fix/assign_test.go new file mode 100644 index 0000000..7f72167 --- /dev/null +++ b/internal/fix/assign_test.go
@@ -0,0 +1,1585 @@ +// 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 fix + +import ( + "testing" +) + +func TestEliminateClearer(t *testing.T) { + tests := []test{ + { + desc: "basic has/set without clearer", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{ + I32: m2.I32, +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} +_ = mypb +`, + }, + }, + + { + desc: "helper and has/set without clearer", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{ + M: &pb2.M2{ + I32: m2.I32, + }, +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +if m2.HasI32() { + m2h2.SetI32(m2.GetI32()) +} +mypb := &pb2.M2{} +mypb.SetM(m2h2) +_ = mypb +`, + }, + }, + + { + desc: "clearer not safe: function call instead of empty composite literal", + srcfiles: []string{"pkg.go"}, + extra: ` +func preparedProto() *pb2.M2 { + result := &pb2.M2{} + result.SetI32(42) + return result +} +`, + in: ` +mypb := preparedProto() +mypb.I32 = m2.I32 +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := preparedProto() +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +_ = mypb +`, + }, + }, + + { + desc: "scope: clearer not safe: function call outside of scope", + srcfiles: []string{"pkg.go"}, + extra: ` +func preparedProto() *pb2.M2 { + result := &pb2.M2{} + result.SetI32(42) + return result +} +`, + in: ` +mypb := preparedProto() +if mypb.GetI32() > 0 { + mypb.I32 = m2.I32 +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := preparedProto() +if mypb.GetI32() > 0 { + if m2.HasI32() { + mypb.SetI32(m2.GetI32()) + } else { + mypb.ClearI32() + } +} +_ = mypb +`, + }, + }, + + { + desc: "scope: message used inside condition body", + srcfiles: []string{"pkg.go"}, + extra: ` +func externalCondition() bool { return true } +`, + in: ` +mypb := &pb2.M2{} +if externalCondition() { + mypb.I32 = m2.I32 +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +if externalCondition() { + if m2.HasI32() { + mypb.SetI32(m2.GetI32()) + } else { + mypb.ClearI32() + } +} +_ = mypb +`, + }, + }, + + { + desc: "scope: clearer not safe due to intermediate proto.Merge", + srcfiles: []string{"pkg.go"}, + extra: ` +func externalCondition() bool { return true } +`, + in: ` +mypb := &pb2.M2{} +if externalCondition() { + proto.Merge(mypb, m2) +} +mypb.I32 = m2.I32 +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +if externalCondition() { + proto.Merge(mypb, m2) +} +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +_ = mypb +`, + }, + }, + + { + desc: "scope: shadowing", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{} +proto.Merge(mypb, m2) +mypb.I32 = m2.I32 +{ + mypb := &pb2.M2{} + mypb.I32 = m2.I32 +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +proto.Merge(mypb, m2) +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +{ + mypb := &pb2.M2{} + if m2.HasI32() { + mypb.SetI32(m2.GetI32()) + } +} +_ = mypb +`, + }, + }, + + { + desc: "plain usage", + extra: `func f() *int32 {return nil }`, + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{} +mypb.I32 = f() +mypb.I32 = m2.I32 +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +mypb.I32 = f() +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +_ = mypb +`, + }, + }, + + { + desc: "setter", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{} +mypb.SetI32(int32(42)) +mypb.I32 = m2.I32 +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +mypb.SetI32(int32(42)) +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +_ = mypb +`, + }, + }, + + { + desc: "direct field assignment", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{} +mypb.I32 = proto.Int32(int32(42)) +mypb.I32 = m2.I32 +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +mypb.SetI32(int32(42)) +if m2.HasI32() { + mypb.SetI32(m2.GetI32()) +} else { + mypb.ClearI32() +} +_ = mypb +`, + }, + }, + + { + desc: "conditional initialization", + srcfiles: []string{"pkg.go"}, + in: ` +_ = func(m *pb2.M2) { + if m == nil { + m = &pb2.M2{} + } + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +_ = func(m *pb2.M2) { + if m == nil { + m = &pb2.M2{} + } + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } else { + m.ClearI32() + } +} +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestProtoToProtoAssignWhenUpdatingOnlyOne(t *testing.T) { + // Before b/266919153, open2opaque would incorrectly not apply some of its + // rewrites when only part of an expression was matched by + // -types_to_update. For example, an assignment m2.I32 = other.I32 would + // only get rewritten correctly if the left *and* right side were in + // -types_to_update. + + tests := []test{ + { + desc: "int32", + typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.OtherProto2": true}, + in: ` +other := new(pb2.OtherProto2) +m2.I32 = other.I32 +`, + want: map[Level]string{ + Red: ` +other := new(pb2.OtherProto2) +if other.HasI32() { + m2.SetI32(other.GetI32()) +} else { + m2.ClearI32() +} +`, + }, + }, + + { + desc: "int32, sides reversed", + typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.OtherProto2": true}, + in: ` +other := new(pb2.OtherProto2) +other.I32 = m2.I32 +`, + want: map[Level]string{ + Red: ` +other := new(pb2.OtherProto2) +if m2.I32 != nil { + other.SetI32(*m2.I32) +} else { + other.ClearI32() +} +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestRemoveLinebreakForShortLines(t *testing.T) { + tests := []test{ + { + desc: "short line", + srcfiles: []string{"pkg.go"}, + extra: `func shortName(*pb2.M2) {}`, + in: ` +shortName( + &pb2.M2{ + I32: proto.Int32(42), + }, +) +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +shortName(m2h2) +`, + }, + }, + + { + desc: "short line, multiple parameters", + srcfiles: []string{"pkg.go"}, + extra: `func shortName(*pb2.M2, *pb2.M2) {}`, + in: ` +shortName( + &pb2.M2{ + I32: proto.Int32(42), + }, + &pb2.M2{ + I32: proto.Int32(42), + }, +) +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +m2h3 := &pb2.M2{} +m2h3.SetI32(42) +shortName(m2h2, m2h3) +`, + }, + }, + + { + desc: "short line, append", + srcfiles: []string{"pkg.go"}, + in: ` +var result []*pb2.M2 +result = append(result, + &pb2.M2{ + I32: proto.Int32(42), + }) +`, + want: map[Level]string{ + Green: ` +var result []*pb2.M2 +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +result = append(result, m2h2) +`, + }, + }, + + { + desc: "long line", + srcfiles: []string{"pkg.go"}, + extra: `func veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine(*pb2.M2) {}`, + in: ` +veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine( + &pb2.M2{ + I32: proto.Int32(42), + }, +) +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +veryLongFunctionNameWhichIfYouCombineItWithItsArgumentsWillLikelyNotComfortablyFitIntoOneLine( + m2h2, +) +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestMultiAssign(t *testing.T) { + tests := []test{{ + desc: "no rewrite when there are no protos involved", + extra: ` +type NotAProto struct { + S *string + Field struct{} +} +var a, b *NotAProto +func g() *string { return nil } +`, + in: "a.S, b.S = nil, g()", + want: map[Level]string{ + Red: "a.S, b.S = nil, g()", + }, + }, { + desc: "multi-clear", + in: ` +m2.B, m2.S, m2.Is, m2.Ms, m2.M, m2.Map = nil, nil, nil, nil, nil, nil +m3.Is, m3.Ms, m3.M, m3.Map = nil, nil, nil, nil +`, + want: map[Level]string{ + Green: ` +m2.B, m2.S, m2.Is, m2.Ms, m2.M, m2.Map = nil, nil, nil, nil, nil, nil +m3.Is, m3.Ms, m3.M, m3.Map = nil, nil, nil, nil +`, + Yellow: ` +m2.ClearB() +m2.ClearS() +m2.SetIs(nil) +m2.SetMs(nil) +m2.ClearM() +m2.SetMap(nil) +m3.SetIs(nil) +m3.SetMs(nil) +m3.ClearM() +m3.SetMap(nil) +`, + }, + }, { + desc: "multi-assign", + in: ` +m2.B, m2.S, m2.Is, m2.Ms, m2.M = proto.Bool(true), proto.String("s"), []int32{1}, []*pb2.M2{{},{}}, &pb2.M2{} +m3.Is, m3.Ms, m3.M = []int32{1}, []*pb3.M3{{},{}}, &pb3.M3{} +`, + want: map[Level]string{ + Green: ` +m2.B, m2.S, m2.Is, m2.Ms, m2.M = proto.Bool(true), proto.String("s"), []int32{1}, []*pb2.M2{{}, {}}, &pb2.M2{} +m3.Is, m3.Ms, m3.M = []int32{1}, []*pb3.M3{{}, {}}, &pb3.M3{} +`, + Yellow: ` +m2.SetB(true) +m2.SetS("s") +m2.SetIs([]int32{1}) +m2.SetMs([]*pb2.M2{{}, {}}) +m2.SetM(&pb2.M2{}) +m3.SetIs([]int32{1}) +m3.SetMs([]*pb3.M3{{}, {}}) +m3.SetM(&pb3.M3{}) +`, + }, + }, { + desc: "multi-assign mixed with non-proto", + in: ` +var n int +_ = n +m2.S, m2.S, n, m2.M = proto.String("s"), nil, 42, &pb2.M2{} +`, + want: map[Level]string{ + Green: ` +var n int +_ = n +m2.S, m2.S, n, m2.M = proto.String("s"), nil, 42, &pb2.M2{} +`, + Yellow: ` +var n int +_ = n +m2.SetS("s") +m2.ClearS() +n = 42 +m2.SetM(&pb2.M2{}) +`, + }, + }, { + desc: "set bytes field", + in: ` +var b []byte +var x bool +m2.Bytes, x = b, true +_ = x +`, + want: map[Level]string{ + Green: ` +var b []byte +var x bool +m2.Bytes, x = b, true +_ = x +`, + Yellow: ` +var b []byte +var x bool +if b != nil { + m2.SetBytes(b) +} else { + m2.ClearBytes() +} +x = true +_ = x +`, + }, + }, { + desc: "proto3", + in: ` +m3.S, m3.M, m3.Is = "", &pb3.M3{}, []int32{1} +`, + want: map[Level]string{ + Green: `m3.S, m3.M, m3.Is = "", &pb3.M3{}, []int32{1}`, + Yellow: ` +m3.SetS("") +m3.SetM(&pb3.M3{}) +m3.SetIs([]int32{1}) +`, + }, + }, { + // Skipped: single multi-valued expressions are not supported yet + desc: "single multi-valued expression, maps", + in: ` +m := map[int]string{} + +m3.S, m3.B = m[1] + +var ok bool +_ = ok +m3.S, ok = m[1] +`, + want: map[Level]string{ + Red: ` +m := map[int]string{} + +m3.S, m3.B = m[1] + +var ok bool +_ = ok +m3.S, ok = m[1] +`, + }, + }, { + // Skipped: single multi-valued expressions are not supported yet + desc: "single multi-valued expression, maps", + in: ` +var s interface{} = "s" + +m3.S, m3.B = s.(string) + +var ok bool +_ = ok +m3.S, ok = s.(string) +`, + want: map[Level]string{ + Red: ` +var s interface{} = "s" + +m3.S, m3.B = s.(string) + +var ok bool +_ = ok +m3.S, ok = s.(string) +`, + }, + }, { + // Skipped: single multi-valued expressions are not supported yet + desc: "single multi-valued expression, maps", + extra: `func g() (string, *bool, bool) { return "", nil, false } `, + in: ` +var ok bool +m3.S, m2.B, ok = g() +_ = ok +`, + want: map[Level]string{ + Red: ` +var ok bool +m3.S, m2.B, ok = g() +_ = ok +`, + }, + }, { + desc: "no rewrite for init simple statement when there are no protos involved", + extra: ` +type NotAProto struct { + S *string + Field struct{} +} +var a, b *NotAProto +func g() *string { return nil } +`, + in: `if a.S, b.S = nil, g(); true { +} +`, + want: map[Level]string{ + Red: `if a.S, b.S = nil, g(); true { +} +`, + }, + }, { + desc: "if init simple statement", + in: ` +if m3.S, m3.M = "", (&pb3.M3{}); m3.B { + m3.B, m3.Is = true, nil +} +`, + want: map[Level]string{ + Red: ` +m3.SetS("") +m3.SetM(&pb3.M3{}) + +if m3.GetB() { + m3.SetB(true) + m3.SetIs(nil) +} +`, + }, + }, { + desc: "for init simple statement", + in: ` +for m3.S, m3.M = "", (&pb3.M3{}); m3.B; { + m3.B, m3.Is = true, nil +} +`, + want: map[Level]string{ + Green: ` +for m3.S, m3.M = "", (&pb3.M3{}); m3.GetB(); { + m3.B, m3.Is = true, nil +} +`, + Yellow: ` +m3.SetS("") +m3.SetM(&pb3.M3{}) + +for m3.GetB() { + m3.SetB(true) + m3.SetIs(nil) +} +`, + }, + }, { + desc: "simple for post statement", + skip: "support multi-assignment in post-statements", + in: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ +} +`, + want: map[Level]string{ + Green: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ +} +`, + Yellow: ` +var n int +for { + n++ + m2.SetS("s") + m3.SetS("s") + +} +`, + }, + }, { + desc: "for post statement + continue", + skip: "support multi-assignment in post-statements", + in: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ + if n % 2==0 { + continue + } +} +`, + want: map[Level]string{ + Green: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ + if n%2 == 0 { + continue + } +} +`, + Yellow: ` +var n int +for { + n++ + if n%2 == 0 { + goto postStmt + + } +postStmt: + m2.SetS("s") + m3.SetS("s") + +} +`, + }, + }, { + desc: "nested loops", + skip: "support multi-assignment in post-statements", + in: ` +for ; ; m3.S, m3.B = "", false { + continue + for { + continue + } +} +`, + want: map[Level]string{ + Green: ` +for ; ; m3.S, m3.B = "", false { + continue + for { + continue + } +} +`, + Yellow: ` +for { + goto postStmt + + for { + continue + } +postStmt: + m3.SetS("") + m3.SetB(false) + +} +`, + }, + }, { + skip: "support nested rewritten loops with multi-assignment post-stmt", + desc: "nested rewritten loops", + in: ` +for ; ; m3.S, m3.B = "", false { + if true { + continue + } + for ; ; m3.S, m3.B = "", false{ + continue + } +} +`, + want: map[Level]string{ + Green: ` +for ; ; m3.S, m3.B = "", false { + if true { + continue + } + for ; ; m3.S, m3.B = "", false { + continue + } +} +`, + Yellow: ` +for { + if true { + goto postStmt + } + for { + goto postStmt2 + poststmt: + m3.SetS("") + m3.SetB(false) + } +postStmt: + m3.SetS("") + m3.SetB(false) +} +`, + }, + }, { + // goto can't jump over declarations + desc: "for post statement + continue + declarations", + skip: "support multi-assignment in post-statements", + in: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ + if n%2==0 { + continue + } + m := 1 + _ = m +} +`, + want: map[Level]string{ + Yellow: ` +var n int +for ; ; m2.S, m3.S = proto.String("s"), "s" { + n++ + if n%2 == 0 { + continue + } + m := 1 + _ = m +} +`, + // This is red because the resulting code is not very + // readable. It's better if it's manually inspected and + // rewritten. + Red: ` +var n int +for ; ; func() { + m2.SetS("s") + m3.SetS("s") +}() { + n++ + if n%2 == 0 { + continue + } + m := 1 + _ = m +} +`, + }, + }, { + desc: "multi-assign field deref", + in: ` +var v int +*m2.S, *m2.I32, v = "hello", 1, 42 +_ = v +`, + want: map[Level]string{ + Red: ` +var v int +m2.SetS("hello") +m2.SetI32(1) +v = 42 +_ = v +`, + }, + }} + + runTableTests(t, tests) +} + +func TestBuildToSetRewrite(t *testing.T) { + const vars = ` +var bytes []byte +var is []int32 +var m2s []*pb2.M2 +var m3s []*pb3.M3 +var m map[string]bool + +var b bool +var f32 float32 +var f64 float64 +var i32 int32 +var i64 int64 +var ui32 uint32 +var ui64 uint64 +var s string +var e2 pb2.M2_Enum +var e3 pb3.M3_Enum + +var bPtr *bool +var f32Ptr *float32 +var f64Ptr *float64 +var i32Ptr *int32 +var i64Ptr *int64 +var ui32Ptr *uint32 +var ui64Ptr *uint64 +var sPtr *string +var e2Ptr *pb2.M2_Enum +` + + tests := []test{ + { + desc: "use builders in tests", + srcfiles: []string{"code_test.go"}, + in: ` +a := []*pb2.M2{ + &pb2.M2{S: nil}, +} +_ = a + +b := &pb2.M2{ + M: &pb2.M2{S: nil}, +} +_ = b + +c := &pb2.M2{ + M: &pb2.M2{ + S: nil, + }, +} +_ = c +`, + want: map[Level]string{ + Yellow: ` +a := []*pb2.M2{ + pb2.M2_builder{S: nil}.Build(), +} +_ = a + +b := pb2.M2_builder{ + M: pb2.M2_builder{S: nil}.Build(), +}.Build() +_ = b + +c := pb2.M2_builder{ + M: pb2.M2_builder{ + S: nil, + }.Build(), +}.Build() +_ = c +`, + }, + }, + + { + desc: "use builders in codelabs", + srcfiles: []string{"spanner_codelab.go"}, + in: ` +a := []*pb2.M2{ + &pb2.M2{S: nil}, +} +_ = a + +b := &pb2.M2{ + M: &pb2.M2{S: nil}, +} +_ = b + +c := &pb2.M2{ + M: &pb2.M2{ + S: nil, + }, +} +_ = c +`, + want: map[Level]string{ + Yellow: ` +a := []*pb2.M2{ + pb2.M2_builder{S: nil}.Build(), +} +_ = a + +b := pb2.M2_builder{ + M: pb2.M2_builder{S: nil}.Build(), +}.Build() +_ = b + +c := pb2.M2_builder{ + M: pb2.M2_builder{ + S: nil, + }.Build(), +}.Build() +_ = c +`, + }, + }, + + { + desc: "use builders if configured", + srcfiles: []string{"code.go"}, + builderTypes: map[string]bool{ + "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2": true, + }, + in: ` +a := []*pb2.M2{ + &pb2.M2{S: nil}, +} +_ = a +`, + want: map[Level]string{ + Green: ` +a := []*pb2.M2{ + pb2.M2_builder{S: nil}.Build(), +} +_ = a +`, + }, + }, + + { + desc: "use builders if too deeply nested", + srcfiles: []string{"code.go"}, + in: ` +_ = &pb2.M2{ + M: &pb2.M2{ + M: &pb2.M2{ + M: &pb2.M2{ + }, // 4 levels of nesting + }, + }, +} + `, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + M: pb2.M2_builder{ + M: pb2.M2_builder{ + M: &pb2.M2{}, // 4 levels of nesting + }.Build(), + }.Build(), +}.Build() +`, + }, + }, + + { + desc: "use builders (shallow nesting)", + srcfiles: []string{"code.go"}, + in: ` +_ = &pb2.M2{ + M: &pb2.M2{ + M: &pb2.M2{ + M: &pb2.M2{ + }, // 4 levels of nesting + }, + }, + Ms: []*pb2.M2{ + &pb2.M2{ + I32: proto.Int32(23), + }, // 3 levels of nesting + }, +} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + M: pb2.M2_builder{ + M: pb2.M2_builder{ + M: &pb2.M2{}, // 4 levels of nesting + }.Build(), + }.Build(), + Ms: []*pb2.M2{ + pb2.M2_builder{ + I32: proto.Int32(23), + }.Build(), // 3 levels of nesting + }, +}.Build() +`, + }, + }, + + { + desc: "use builders if too many messages are involved", + srcfiles: []string{"code.go"}, + in: ` +_ = &pb2.M2{ + Ms: []*pb2.M2{ + // four proto messages involved in the literal: + &pb2.M2{I32: proto.Int32(23)}, + &pb2.M2{I32: proto.Int32(23)}, + &pb2.M2{I32: proto.Int32(23)}, + &pb2.M2{I32: proto.Int32(23)}, + }, +} + `, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + Ms: []*pb2.M2{ + // four proto messages involved in the literal: + pb2.M2_builder{I32: proto.Int32(23)}.Build(), + pb2.M2_builder{I32: proto.Int32(23)}.Build(), + pb2.M2_builder{I32: proto.Int32(23)}.Build(), + pb2.M2_builder{I32: proto.Int32(23)}.Build(), + }, +}.Build() +`, + }, + }, + + { + desc: "builders for one-liners in tests", + srcfiles: []string{"code_test.go"}, + in: ` +a := &pb2.M2{S: nil} +_ = a + +b := &pb2.M2{ + S: nil, +} +_ = b +`, + want: map[Level]string{ + Yellow: ` +a := pb2.M2_builder{S: nil}.Build() +_ = a + +b := pb2.M2_builder{ + S: nil, +}.Build() +_ = b +`, + }, + }, + + { + desc: "setters for one-liners outside tests", + srcfiles: []string{"code.go"}, + in: ` +a := &pb2.M2{S:nil} +_ = a + +b := &pb2.M2{ + S:nil, +} +_ = b +`, + want: map[Level]string{ + Yellow: ` +a := &pb2.M2{} +a.ClearS() +_ = a + +b := &pb2.M2{} +b.ClearS() +_ = b +`, + }, + }, + + { + desc: "clit to non-builder: proto2: new objs", + srcfiles: []string{"code.go"}, + in: ` +mm2 := &pb2.M2{ + B: proto.Bool(true), + Bytes: []byte("hello"), + F32: proto.Float32(1), + F64: proto.Float64(2), + I32: proto.Int32(3), + I64: proto.Int64(4), + Ui32: proto.Uint32(5), + Ui64: proto.Uint64(6), + S: proto.String("world"), + M: &pb2.M2{}, + Is: []int32{10, 11}, + Ms: []*pb2.M2{{}, {}}, + Map: map[string]bool{"a": true}, + E: pb2.M2_E_VAL.Enum(), +} +_ = mm2`, + want: map[Level]string{ + Yellow: ` +mm2 := &pb2.M2{} +mm2.SetB(true) +mm2.SetBytes([]byte("hello")) +mm2.SetF32(1) +mm2.SetF64(2) +mm2.SetI32(3) +mm2.SetI64(4) +mm2.SetUi32(5) +mm2.SetUi64(6) +mm2.SetS("world") +mm2.SetM(&pb2.M2{}) +mm2.SetIs([]int32{10, 11}) +mm2.SetMs([]*pb2.M2{{}, {}}) +mm2.SetMap(map[string]bool{"a": true}) +mm2.SetE(pb2.M2_E_VAL) +_ = mm2 +`, + }, + }, + + { + desc: "clit to non-builder: proto3: new objs", + srcfiles: []string{"code.go"}, + in: ` +mm3 := &pb3.M3{ + B: true, + Bytes: []byte("hello"), + F32: 1, + F64: 2, + I32: 3, + I64: 4, + Ui32: 5, + Ui64: 6, + S: "world", + M: &pb3.M3{}, + Is: []int32{10, 11}, + Ms: []*pb3.M3{{}, {}}, + Map: map[string]bool{"a": true}, + E: pb3.M3_E_VAL, +} +_ = mm3 +`, + want: map[Level]string{ + Yellow: ` +mm3 := &pb3.M3{} +mm3.SetB(true) +mm3.SetBytes([]byte("hello")) +mm3.SetF32(1) +mm3.SetF64(2) +mm3.SetI32(3) +mm3.SetI64(4) +mm3.SetUi32(5) +mm3.SetUi64(6) +mm3.SetS("world") +mm3.SetM(&pb3.M3{}) +mm3.SetIs([]int32{10, 11}) +mm3.SetMs([]*pb3.M3{{}, {}}) +mm3.SetMap(map[string]bool{"a": true}) +mm3.SetE(pb3.M3_E_VAL) +_ = mm3 +`, + }}, + + { + desc: "clit to non-builder: proto2 vars", + srcfiles: []string{"code.go"}, + extra: vars, + in: ` +mm2 := &pb2.M2{ + M: m2, + Is: is, + Ms: m2s, + Map: m, +} +_ = mm2 +`, + want: map[Level]string{ + Yellow: ` +mm2 := &pb2.M2{} +mm2.SetM(m2) +mm2.SetIs(is) +mm2.SetMs(m2s) +mm2.SetMap(m) +_ = mm2 +`, + }, + }, + + { + desc: "clit to non-builder: proto3 vars", + srcfiles: []string{"code.go"}, + extra: vars, + in: ` +mm3 := &pb3.M3{ + B: b, + F32: f32, + F64: f64, + I32: i32, + I64: i64, + Ui32: ui32, + Ui64: ui64, + S: s, + M: &pb3.M3{}, + Is: is, + Ms: m3s, + Map: m, + E: e3, +} +_ = mm3 +`, + want: map[Level]string{ + Yellow: ` +mm3 := &pb3.M3{} +mm3.SetB(b) +mm3.SetF32(f32) +mm3.SetF64(f64) +mm3.SetI32(i32) +mm3.SetI64(i64) +mm3.SetUi32(ui32) +mm3.SetUi64(ui64) +mm3.SetS(s) +mm3.SetM(&pb3.M3{}) +mm3.SetIs(is) +mm3.SetMs(m3s) +mm3.SetMap(m) +mm3.SetE(e3) +_ = mm3 +`}}, { + desc: "clit to non-builder: preserve proto2 presence from message", + srcfiles: []string{"code.go"}, + extra: vars, + in: ` +mm2 := &pb2.M2{ + Bytes: m2.Bytes, // eol comment + B: m2.B, + F32: m2.F32, + F64: m2.F64, + I32: m2.I32, + I64: m2.I64, + Ui32: m2.Ui32, + Ui64: m2.Ui64, + S: m2.S, + E: m2.E, +} +_ = mm2 +`, + want: map[Level]string{ + Yellow: ` +mm2 := &pb2.M2{} +// eol comment +if x := m2.GetBytes(); x != nil { + mm2.SetBytes(x) +} +if m2.HasB() { + mm2.SetB(m2.GetB()) +} +if m2.HasF32() { + mm2.SetF32(m2.GetF32()) +} +if m2.HasF64() { + mm2.SetF64(m2.GetF64()) +} +if m2.HasI32() { + mm2.SetI32(m2.GetI32()) +} +if m2.HasI64() { + mm2.SetI64(m2.GetI64()) +} +if m2.HasUi32() { + mm2.SetUi32(m2.GetUi32()) +} +if m2.HasUi64() { + mm2.SetUi64(m2.GetUi64()) +} +if m2.HasS() { + mm2.SetS(m2.GetS()) +} +if m2.HasE() { + mm2.SetE(m2.GetE()) +} +_ = mm2 +`}, + }, + + { + desc: "clit to non-builder: preserve proto2 presence from var", + srcfiles: []string{"code.go"}, + extra: vars, + in: ` +mm2 := &pb2.M2{ + Bytes: bytes, + B: bPtr, + F32: f32Ptr, + F64: f64Ptr, + I32: i32Ptr, + I64: i64Ptr, + Ui32: ui32Ptr, + Ui64: ui64Ptr, + S: sPtr, + E: e2Ptr, +} +_ = mm2 +`, + want: map[Level]string{ + Yellow: ` +mm2 := &pb2.M2{} +if bytes != nil { + mm2.SetBytes(bytes) +} +mm2.B = bPtr +mm2.F32 = f32Ptr +mm2.F64 = f64Ptr +mm2.I32 = i32Ptr +mm2.I64 = i64Ptr +mm2.Ui32 = ui32Ptr +mm2.Ui64 = ui64Ptr +mm2.S = sPtr +if e2Ptr != nil { + mm2.SetE(*e2Ptr) +} +_ = mm2 +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestBuildersAreGreenInTests(t *testing.T) { + tt := test{ + in: ` +_ = []*pb2.M2{ + &pb2.M2{S: nil}, +} +`, + want: map[Level]string{ + Green: ` +_ = []*pb2.M2{ + pb2.M2_builder{S: nil}.Build(), +} +`, + }, + } + + runTableTest(t, tt) +} + +// TestAssignOperations tests that assignment operations like token.ADD_ASSIGN (+=) are rewritten. +func TestAssignOperations(t *testing.T) { + tests := []test{ + { + desc: "ADD_ASSIGN proto2 int32", + in: ` +*m2.I32 += 5 +`, + want: map[Level]string{ + Green: ` +m2.SetI32(m2.GetI32() + 5) +`, + }, + }, + { + desc: "SUB_ASSIGN proto3 int64", + in: ` +m3.I64 -= 5 +`, + want: map[Level]string{ + Green: ` +m3.SetI64(m3.GetI64() - 5) +`, + }, + }, + { + desc: "never nil enum", + srcfiles: []string{"pkg.go"}, + in: ` +var b pb2.M2_Enum +m2.E = &b +`, + want: map[Level]string{ + Green: ` +var b pb2.M2_Enum +m2.SetE(b) +`, + }, + }, + { + desc: "QUO_ASSIGN proto2 float64 end-of-line-comment", + in: ` +*m2.F64 /= 5. // comment +`, + want: map[Level]string{ + Green: ` +m2.SetF64(m2.GetF64() / 5.) // comment +`, + }, + }, + { + desc: "AND_ASSIGN proto2 uint32 newline-before", + in: ` +_ = "something" + +*m2.Ui32 &= 42 +`, + want: map[Level]string{ + Green: ` +_ = "something" + +m2.SetUi32(m2.GetUi32() & 42) +`, + }, + }, + { + desc: "SHL_ASSIGN proto3 uint64 newline-after", + in: ` +m3.Ui64 <<= 2 + +_ = "something" +`, + want: map[Level]string{ + Green: ` +m3.SetUi64(m3.GetUi64() << 2) + +_ = "something" +`, + }, + }, + { + desc: "string-concatenation proto2", + in: ` +*m2.S = "hello " +*m2.S += "world!" +`, + want: map[Level]string{ + Green: ` +m2.SetS("hello ") +m2.SetS(m2.GetS() + "world!") +`, + }, + }, + { + desc: "non-proto DEFINE AssignStmt no-rewrite", + in: ` +x := 5 +_ = x +`, + want: map[Level]string{ + Green: ` +x := 5 +_ = x +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestNilCheck(t *testing.T) { + tt := test{ + desc: "side effect free expression", + extra: `type mytype struct { e *pb2.M2_Enum }`, + in: ` +mt := mytype{} +m := &pb2.M2{} +m.E = mt.e +_ = m +`, + want: map[Level]string{ + Green: ` +mt := mytype{} +m := &pb2.M2{} +if mt.e != nil { + m.SetE(*mt.e) +} +_ = m +`, + }, + } + + runTableTest(t, tt) +}
diff --git a/internal/fix/assignswap.go b/internal/fix/assignswap.go new file mode 100644 index 0000000..0dee837 --- /dev/null +++ b/internal/fix/assignswap.go
@@ -0,0 +1,149 @@ +// 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 fix + +import ( + "fmt" + "go/token" + "strings" + + "github.com/dave/dst" +) + +// invariant: selFromExpr() must only be called on expressions for which +// c.protoFieldSelectorOrAccessor() returns ok. assignmentIsSwap() ensures that +// for all lhs/rhs expressions. +func selFromExpr(c *cursor, expr dst.Expr) *dst.SelectorExpr { + if ce, ok := expr.(*dst.CallExpr); ok { + sel, _, _ := c.protoFieldSelectorOrAccessor(ce.Fun) + return sel + } + return expr.(*dst.SelectorExpr) +} + +func idFromExpr(expr dst.Expr) *dst.Ident { + if expr == nil { + return nil + } + switch x := expr.(type) { + case *dst.Ident: + return x + case *dst.CallExpr: + return idFromExpr(x.Fun) + case *dst.SelectorExpr: + return idFromExpr(x.Sel) + default: + return nil + } +} + +func assignmentIsSwap(c *cursor, stmt *dst.AssignStmt) error { + if stmt.Tok != token.ASSIGN { + return fmt.Errorf("ignoring AssignStmt with Tok %v (looking for ASSIGN)", stmt.Tok) + } + + if len(stmt.Lhs) != len(stmt.Rhs) { + return fmt.Errorf("ignoring AssignStmt with len(lhs)=%d != len(rhs)=%d (cannot be a swap)", len(stmt.Lhs), len(stmt.Rhs)) + } + + if len(stmt.Lhs) == 1 { + return fmt.Errorf("ignoring AssignStmt with 1 lhs/rhs (assignment, not swap)") + } + + for _, expr := range append(append([]dst.Expr(nil), stmt.Lhs...), stmt.Rhs...) { + if ce, ok := expr.(*dst.CallExpr); ok { + expr = ce.Fun + } + se, ok := expr.(*dst.SelectorExpr) + if !ok { + return fmt.Errorf("ignoring AssignStmt: %T is not a SelectorExpr (cannot be a swap)", expr) + } + if idFromExpr(se.X) == nil { + return fmt.Errorf("ignoring AssignStmt: could not find Ident in SelectorExpr.X %T", expr) + } + sel, _, ok := c.protoFieldSelectorOrAccessor(expr) + if !ok { + return fmt.Errorf("ignoring AssignStmt: %T is not a proto field selector", expr) + } + t := c.typeOfOrNil(sel.X) + if t == nil { + continue // no type info (silo'ed?), assume tracked + } + if !c.shouldUpdateType(t) { + return fmt.Errorf("should not update type %v", t) + } + } + + c.Logf("rewriting swap") + + // As soon as we find any repetition among the left and right hand side, we + // treat the assignment as a swap. + for _, lhs := range stmt.Lhs { + lhsSel := selFromExpr(c, lhs) + lhsXObj := c.objectOf(idFromExpr(lhsSel.X)) + lhsSelName := c.objectOf(lhsSel.Sel).Name() + for _, rhs := range stmt.Rhs { + rhsSel := selFromExpr(c, rhs) + rhsXObj := c.objectOf(idFromExpr(rhsSel.X)) + rhsSelName := c.objectOf(rhsSel.Sel).Name() + // If the RHS is a function call (LHS of an assignment cannot be a + // function call), it must be a getter (the other accessors do not + // return a value), so remove the Get prefix to match the LHS name. + if _, ok := rhs.(*dst.CallExpr); ok { + rhsSelName = strings.TrimPrefix(rhsSelName, "Get") + } + + if lhsXObj == rhsXObj && lhsSelName == rhsSelName { + return nil + } + } + } + return fmt.Errorf("ignoring AssignStmt: no repetitions among lhs/rhs") +} + +// assignSwapPre splits swaps (m.F1, m.F2 = m.F2, m.F1) into two separate assign +// statements which can then be rewritten by subsequent rewrite stages. +func assignSwapPre(c *cursor) bool { + // NOTE(stapelberg): This stage is only yellow level because the subsequent + // getPost and assignPre stages only deal with pointer-typed variables in + // the yellow level. But, safety-wise, this could go into the green level. + if !c.lvl.ge(Yellow) { + return true + } + + stmt, ok := c.Node().(*dst.AssignStmt) + if !ok { + c.Logf("ignoring %T (looking for AssignStmt)", c.Node()) + return true + } + if err := assignmentIsSwap(c, stmt); err != nil { + c.Logf("%s", err.Error()) + return true + } + + assign2 := &dst.AssignStmt{ + Lhs: stmt.Lhs, + Tok: token.ASSIGN, + Rhs: nil, // will be filled with helper variable names + } + stmt.Lhs = nil // will be filled with helper variable names + for _, rhs := range stmt.Rhs { + rhsSel := selFromExpr(c, rhs) + helperName := c.helperNameFor(rhs, c.typeOf(rhsSel.X)) + helperIdent := &dst.Ident{Name: helperName} + updateASTMap(c, rhs, helperIdent) + c.setType(helperIdent, c.typeOf(rhs)) + stmt.Lhs = append(stmt.Lhs, helperIdent) + assign2.Rhs = append(assign2.Rhs, cloneIdent(c, helperIdent)) + } + stmt.Tok = token.DEFINE + assign2.Decorations().After = stmt.Decorations().After + assign2.Decorations().End = stmt.Decorations().End + stmt.Decorations().After = dst.None + stmt.Decorations().End = nil + c.InsertAfter(assign2) + + return true +}
diff --git a/internal/fix/assignswap_test.go b/internal/fix/assignswap_test.go new file mode 100644 index 0000000..689b540 --- /dev/null +++ b/internal/fix/assignswap_test.go
@@ -0,0 +1,230 @@ +// 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 fix + +import "testing" + +func TestSetterSwap(t *testing.T) { + tests := []test{ + { + desc: "int32 swap", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{ + I32: proto.Int32(23), + Build: proto.Int32(42), +} +mypb.I32, mypb.Build = mypb.Build, mypb.I32 +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +mypb.SetI32(23) +mypb.SetBuild_(42) +mypb.I32, mypb.Build = mypb.Build, mypb.I32 +`, + Yellow: ` +mypb := &pb2.M2{} +mypb.SetI32(23) +mypb.SetBuild_(42) +m2h2, m2h3 := proto.ValueOrNil(mypb.HasBuild_(), mypb.GetBuild_), proto.ValueOrNil(mypb.HasI32(), mypb.GetI32) +mypb.I32 = m2h2 +mypb.Build = m2h3 +`, + Red: ` +mypb := &pb2.M2{} +mypb.SetI32(23) +mypb.SetBuild_(42) +m2h2, m2h3 := proto.ValueOrNil(mypb.HasBuild_(), mypb.GetBuild_), proto.ValueOrNil(mypb.HasI32(), mypb.GetI32) +if m2h2 != nil { + mypb.SetI32(*m2h2) +} else { + mypb.ClearI32() +} +if m2h3 != nil { + mypb.SetBuild_(*m2h3) +} else { + mypb.ClearBuild_() +} +`, + }, + }, + + { + desc: "int32 oneof copy", + srcfiles: []string{"pkg.go"}, + extra: `func defaultVal() *pb2.M2 { return nil }`, + in: ` +mypb := &pb2.M2{ + M: &pb2.M2{ + OneofField: &pb2.M2_IntOneof{ + IntOneof: 23, + }, + }, +} +mypb.M.OneofField = defaultVal().M.OneofField +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetIntOneof(23) +mypb := &pb2.M2{} +mypb.SetM(m2h2) +mypb.GetM().OneofField = defaultVal().GetM().OneofField +`, + Yellow: ` +m2h2 := &pb2.M2{} +m2h2.SetIntOneof(23) +mypb := &pb2.M2{} +mypb.SetM(m2h2) +mypb.GetM().OneofField = defaultVal().GetM().OneofField +`, + }, + }, + + { + desc: "int32 proto3 getter swap", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := pb3.M3_builder{ + I32: 23, + SecondI32: 42, +}.Build() +mypb.I32, mypb.SecondI32 = mypb.GetSecondI32(), mypb.GetI32() +`, + want: map[Level]string{ + Green: ` +mypb := pb3.M3_builder{ + I32: 23, + SecondI32: 42, +}.Build() +mypb.I32, mypb.SecondI32 = mypb.GetSecondI32(), mypb.GetI32() +`, + Yellow: ` +mypb := pb3.M3_builder{ + I32: 23, + SecondI32: 42, +}.Build() +m3h2, m3h3 := mypb.GetSecondI32(), mypb.GetI32() +mypb.SetI32(m3h2) +mypb.SetSecondI32(m3h3) +`, + Red: ` +mypb := pb3.M3_builder{ + I32: 23, + SecondI32: 42, +}.Build() +m3h2, m3h3 := mypb.GetSecondI32(), mypb.GetI32() +mypb.SetI32(m3h2) +mypb.SetSecondI32(m3h3) +`, + }, + }, + + { + desc: "int32 nested proto3 getter swap", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), +}.Build() +mypb.M.I32, mypb.M.SecondI32 = mypb.M.GetSecondI32(), mypb.M.GetI32() + `, + want: map[Level]string{ + Green: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), +}.Build() +mypb.GetM().I32, mypb.GetM().SecondI32 = mypb.GetM().GetSecondI32(), mypb.GetM().GetI32() +`, + Yellow: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), +}.Build() +m3h2, m3h3 := mypb.GetM().GetSecondI32(), mypb.GetM().GetI32() +mypb.GetM().SetI32(m3h2) +mypb.GetM().SetSecondI32(m3h3) +`, + Red: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), +}.Build() +m3h2, m3h3 := mypb.GetM().GetSecondI32(), mypb.GetM().GetI32() +mypb.GetM().SetI32(m3h2) +mypb.GetM().SetSecondI32(m3h3) +`, + }, + }, + + { + desc: "int32 double-nested proto3 getter swap", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), + }.Build(), +}.Build() +mypb.M.M.I32, mypb.M.M.SecondI32 = mypb.M.M.GetSecondI32(), mypb.M.M.GetI32() +`, + want: map[Level]string{ + Green: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), + }.Build(), +}.Build() +mypb.GetM().GetM().I32, mypb.GetM().GetM().SecondI32 = mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32() +`, + Yellow: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), + }.Build(), +}.Build() +m3h2, m3h3 := mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32() +mypb.GetM().GetM().SetI32(m3h2) +mypb.GetM().GetM().SetSecondI32(m3h3) +`, + Red: ` +mypb := pb3.M3_builder{ + M: pb3.M3_builder{ + M: pb3.M3_builder{ + I32: 23, + SecondI32: 42, + }.Build(), + }.Build(), +}.Build() +m3h2, m3h3 := mypb.GetM().GetM().GetSecondI32(), mypb.GetM().GetM().GetI32() +mypb.GetM().GetM().SetI32(m3h2) +mypb.GetM().GetM().SetSecondI32(m3h3) +`, + }, + }, + } + + runTableTests(t, tests) +}
diff --git a/internal/fix/build.go b/internal/fix/build.go new file mode 100644 index 0000000..19688ba --- /dev/null +++ b/internal/fix/build.go
@@ -0,0 +1,433 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" +) + +// buildPost rewrites the code to use builders for object construction. This +// function is executed by traversing the tree in postorder. It visits every +// node (always returns true to continue the traversal) because Builder calls can +// be nested. +func buildPost(c *cursor) bool { + // &pb.M{F: V} => pb.M_builder{F: V}.Build() + if _, ok := c.Node().(*dst.UnaryExpr); ok { + if lit, ok := c.builderCLit(c.Node(), c.Parent()); ok { + if !c.useBuilder(lit) { + c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit)) + return true + } + expr := c.Node().(dst.Expr) + incompleteRewrite := !updateBuilderElements(c, lit) + if incompleteRewrite && !c.lvl.ge(Red) { + c.Logf("returning, no builder elements updated") + return true + } + if call, ok := newBuildCall(c, c.typeOf(expr), lit.Type, lit, *expr.Decorations()); ok { + if incompleteRewrite { + c.ReplaceUnsafe(call, IncompleteRewrite) + } else { + c.Replace(call) + } + c.Logf("successfully generated builder") + } + return true + } + } + + // K: {F: V} => K: pb.M_builder{F: V}.Build() for map KVs and slice/array KVs + // + // We assert on the key value here and not on the composite literal because: + // - we can only access the parent of the node, but not its grandparent + // - the relevant container, in case of KV composite literal values, is a grandparent as the KV is the parent. + // An alternative implementation would track parents of all nodes. + if kv, ok := c.Node().(*dst.KeyValueExpr); ok { + lit, ok := kv.Value.(*dst.CompositeLit) + if !ok || lit.Type != nil || len(lit.Elts) == 0 || !c.shouldUpdateType(c.typeOf(lit)) || !isPtr(c.typeOf(lit)) { + return true + } + if !c.useBuilder(lit) { + c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit)) + return true + } + typ, ok := parentValPBType(c, kv) + if !ok { + return true + } + incompleteRewrite := !updateBuilderElements(c, lit) + if incompleteRewrite && !c.lvl.ge(Red) { + return true + } + if call, ok := newBuildCall(c, c.typeOf(lit), typ, lit, dst.NodeDecs{}); ok { + kv.Value = call + if incompleteRewrite { + c.ReplaceUnsafe(kv, IncompleteRewrite) + } else { + c.Replace(kv) + } + } + return true + } + + // {F: V} => pb.M_builder{F: V}.Build() when {F: V} is a protobuf (e.g. "[]*pb.M{{F0: V0}, {F1:V1}}") + lit, ok := c.Node().(*dst.CompositeLit) + if !ok || lit.Type != nil || len(lit.Elts) == 0 || !c.shouldUpdateType(c.typeOf(lit)) || !isPtr(c.typeOf(lit)) { + return true + } + if !c.useBuilder(lit) { + c.Logf("requested to not use builders for this file or type %v", c.typeOf(lit)) + return true + } + typ, ok := parentValPBType(c, lit) + if !ok { + return true + } + incompleteRewrite := !updateBuilderElements(c, lit) + if incompleteRewrite && !c.lvl.ge(Red) { + return true + } + if call, ok := newBuildCall(c, c.typeOf(lit), typ, lit, dst.NodeDecs{}); ok { + if incompleteRewrite { + c.ReplaceUnsafe(call, IncompleteRewrite) + } else { + c.Replace(call) + } + } + return true +} + +func isNeverNilExpr(c *cursor, e dst.Expr) bool { + // Is this taking the address of something? + if ue, ok := e.(*dst.UnaryExpr); ok && ue.Op == token.AND { + return true + } + // Is this a builder call? + if ce, ok := e.(*dst.CallExpr); ok && len(ce.Args) == 0 { + sel, ok := ce.Fun.(*dst.SelectorExpr) + if !ok { + return false + } + if !c.isBuilderType(c.typeOf(sel.X)) { + return false + } + // As of 2023-06-29 there is only one function defined on the builder. + // Thus, technically this check is not needed but it is a second layer + // of defense. + if sel.Sel.Name != "Build" { + return false + } + return true + } + return false +} + +// updateBuilderElements does necessary rewrites to elements when changing a +// composite literal into a builder. +// +// Returns ok==true if all elements could be handled and ok==false if there was +// a case that we can't handle yet and hence didn't rewrite anything. +func updateBuilderElements(c *cursor, lit *dst.CompositeLit) (ok bool) { + // A list of updates to execute if there are no hard cases. + var updates []func() + + // Handle oneof fields in builders. + // + // pb.M{F: pb.M_Oneof{K: V}} + // pb.M{F: pb.M_Oneof{V}} + // pb.M{F: pb.M_Oneof{}} + // => + // pb.M_builder{K: V'}.Build() + // + // Where + // + // F used to be the made up "oneof field" + // K is the name of the only field in the oneof wrapper for the field + // V' is V made into a pointer for basic types (it's a pointer already for + // other types). If V is not present then this is a pointer to the zero + // value for basic types and no rewrite for enums/messages. + for _, e := range lit.Elts { + c.Logf("updating composite literal element") + kv, ok := e.(*dst.KeyValueExpr) + if !ok { + c.Logf("skipping %T (looking for KeyValueExpr)", e) + continue + } + + // Skip over fields that are not oneof. + if _, ok := c.underlyingTypeOf(kv.Key).(*types.Interface); !ok { + c.Logf("skipping none oneof field", e) + continue + } + + // Check that the value is a oneof that we can rewrite: address of a + // composite literal with exactly one field that has a "oneof" tag + fieldName, fieldType, fieldValue, decs, ok := destructureOneofWrapper(c, kv.Value) + if !ok { + // RHS is not a oneof wrapper but a oneof field itself. + // Try generating an exhaustive list covering all cases. + updates, ok = generateOneofBuilderCases(c, updates, lit, kv) + if !ok { + c.Logf("failed to generate exhaustive list of oneof cases") + return false + } + // At this point we know that we can replace the oneof field with + // key value pairs for its cases. + e := e + updates = append(updates, func() { + var idx int + // We know that this always find the element because there is + // exactly one attempt to rewrite this KeyValueExpr. + for i, ne := range lit.Elts { + if e == ne { + idx = i + } + } + // Remove the KeyValueExpr for the oneof field since it was + // replaced by KeyValueExpr's for all cases in + // generateOneofBuilderCases(). + lit.Elts = append(lit.Elts[:idx], lit.Elts[idx+1:]...) + }) + c.Logf("generated exhaustive list of oneof cases") + continue + } + + // Don't rewrite the oneof in + // + // &pb.M2{OneofField: &pb.M2_MsgOneof{}} + // &pb.M2{OneofField: &pb.M2_EnumOneof{}} + // + // It's a tricky case and should be very rare because "oneof + // with a type but without a value" is not a valid protocol buffers + // concept. + // + // It happens due to a mismatch between what's allowed in protocol buffers + // and the Go structs representing protocol buffers in the open API. + // This information will not be marshalled to the wire and the field + // is effectively discarded during marshalling when it does not have + // a value. + // + // The opaque API does not allow this. While the struct can technically + // represent this, you cannot use the API to bring the struct into + // this state. + // + // For enums: this should be rare and we don't want to guess the default + // value. + if fieldValue == nil && !isBasic(fieldType) { + c.Logf("returning: RHS is nil of Type %T (looking for types.Basic)", fieldType) + return false + } + + // If the wrapped value can be nil, it is generally not safe to + // rewrite because this changes behavior from a set oneof field with + // type but no value to a completely unset oneof. + unsafeRewrite := false + if !isNeverNilExpr(c, fieldValue) && !isNeverNilSliceExpr(c, fieldValue) && !isBasic(fieldType) && !isEnum(fieldType) { + if !c.lvl.ge(Yellow) { + c.Logf("returning: RHS is nil of Type %T (looking for types.Basic)", fieldType) + return false + } + unsafeRewrite = true + } + + // Handle `M{Oneof: OneofBasicField{}}` + if fieldValue == nil { + updates = append(updates, func() { + kv.Key.(*dst.Ident).Name = fieldName // Rename the key to field name from the oneof wrapper. + kv.Value = c.newProtoHelperCall(nil, fieldType.(*types.Basic)) + }) + c.Logf("generated RHS for field %v", fieldName) + continue + } + + // We don't handle assigning literal integers to enum fields: + // + // 1. This is a rare way to set enums (most code uses enum + // constants, not integer literals) + // + // 2. Handling this case requires a lot of extra machinery. We + // must be able to construct AST by knowing only the type + // that we want. This requires inspecting imports, ensuring + // that they are not shadowed, and potentially adding new + // imports. + t := c.typeOf(fieldValue) + if _, ok := fieldValue.(*dst.BasicLit); ok && isEnum(t) { + c.Logf("returning: assignment of int literal to enum") + return false + } + + // If it's not a pointer and not []byte then make it a pointer + // because everything in the builder is a pointer. + // + // In practice, isPtr checks if the type is a message because + // non-pointer, non-[]byte types in oneof wrappers are either + // enums (*types.Named) or basic types (*types.Basic). + if !isPtr(t) && !isSlice(t) { + fieldValue = c.newProtoHelperCall(fieldValue, t) + } + + updates = append(updates, func() { + if decs != nil { + kv.Decorations().Start = append(kv.Decorations().Start, decs.Start...) + kv.Decorations().End = append(kv.Decorations().End, decs.End...) + } + kv.Key.(*dst.Ident).Name = fieldName // Rename the key to field name from the oneof wrapper. + kv.Value = fieldValue + if unsafeRewrite { + c.numUnsafeRewritesByReason[MaybeOneofChange]++ + } + }) + } + + // If we are here then we're confident that we can rewrite the composite + // literal to a builder. + + for _, u := range updates { + u() + } + + // Rename fields to deal with naming conflicts: + for _, e := range lit.Elts { + kv, ok := e.(*dst.KeyValueExpr) + if !ok { + continue + } + sel, ok := kv.Key.(*dst.Ident) + if !ok { + continue + } + sel.Name = fixConflictingNames(c.typeOf(lit), "", sel.Name) + } + + c.Logf("updated of expressions of composite literal") + return true +} + +// parentValPBType returns the expression representing the type of x in the parent. For example: +// +// In: +// []*pb.M{ // [1] +// {M:nil}, // [2] +// } +// the return value for composite literal "{M:nil}" from line [2] is "pb.M" from line [1]. +// +// In: +// +// map[int]*pb.M{ // [1] +// 0: {M:nil}, // [2] +// } +// the return value for key-value expression "0: {M:nil}" on line [2] is "pb.M" from line [1]. +func parentValPBType(c *cursor, x dst.Expr) (dst.Expr, bool) { + plit, ok := c.Parent().(*dst.CompositeLit) + if !ok { + return nil, false + } + var typ dst.Expr + switch t := plit.Type.(type) { + case *dst.ArrayType: + typ = t.Elt + case *dst.MapType: + typ = t.Value + default: + return nil, false + } + se, ok := typ.(*dst.StarExpr) + if !ok { + return nil, false + } + return se.X, true +} + +// newBuildCall wraps the provided elements in builder Build call for the provided type. +// t is the type of Build() result (pointer to a message struct). +// typ is DST representing the protobuf struct type (e.g. selector "pb.M"). +// lit is the source composite literal (e.g. "pb.M{F: V}"). It has a non-zero number of elements. +func newBuildCall(c *cursor, t types.Type, typ dst.Expr, lit *dst.CompositeLit, parentDecs dst.NodeDecs) (dst.Expr, bool) { + sel, ok := typ.(*dst.SelectorExpr) + if !ok { + // Could happen if someone creates a new named type in their package. For example: + // type MyMsg pb.M + return nil, false + } + + msgType := types.NewPointer(c.typeOf(sel.Sel)) // *pb.M + builder := &dst.SelectorExpr{ + // Clone the selector in case we might duplicate it when we rewrite: + // + // []*pb.M{{F:nil}, {F:nil}} + // + // to: + // + // []*pb.M{ + // pb.M_builder{F:nil}.Build(), + // pb.M_builder{F:nil}.Build(), + // } + X: cloneSelectorExpr(c, sel).X, + Sel: &dst.Ident{Name: sel.Sel.Name + "_builder"}, + } + pkg := c.objectOf(sel.Sel).Pkg() + builderType := types.NewNamed( // pb.M_builder in the same package as pb.M + types.NewTypeName(token.NoPos, pkg, sel.Sel.Name+"_builder", nil), + types.NewStruct(nil, nil), + nil) + builderType.AddMethod(types.NewFunc(token.NoPos, pkg, "Build", types.NewSignature( // func (pb.M_Builder) Build() *pb.M + types.NewParam(token.NoPos, pkg, "_", builderType), + types.NewTuple(), + types.NewTuple(types.NewParam(token.NoPos, pkg, "_", msgType)), + false))) + c.setType(builder, builderType) + updateASTMap(c, typ, builder) + c.setType(builder.Sel, builderType) + + builderLit := &dst.CompositeLit{ + Type: builder, + Elts: lit.Elts, + } + c.setType(builderLit, builderType) // pb.M_builder{...} has the same type as pb.M_builder + updateASTMap(c, typ, builderLit) + + // pb.M_builder{...}.Build is the only pb.M_builder method. + fun := &dst.SelectorExpr{ + X: builderLit, + Sel: &dst.Ident{Name: "Build"}, + } + c.setType(fun, builderType.Method(0).Type()) + c.setType(fun.Sel, builderType.Method(0).Type()) + updateASTMap(c, typ, fun) + + // pb.M_builder{...}.Build() returns *pb.M + buildCall := &dst.CallExpr{Fun: fun} + c.setType(buildCall, msgType) + updateASTMap(c, typ, buildCall) + + // Update decorations (comments and whitespace). + builderLit.Decs = lit.Decs + builderLit.Decs.After = dst.None + builderLit.Decs.End = nil + + decs := dst.NodeDecs{ + After: lit.Decs.After, + End: lit.Decs.End, + } + if b := parentDecs.Before; b != dst.None { + decs.Before = b + } + decs.Start = append(parentDecs.Start, decs.Start...) + decs.End = append(decs.End, parentDecs.End...) + if a := parentDecs.After; a != dst.None { + decs.After = a + } + buildCall.Decs.NodeDecs = decs + + return buildCall, true +} + +func isSlice(t types.Type) bool { + _, ok := t.Underlying().(*types.Slice) + return ok +}
diff --git a/internal/fix/build_test.go b/internal/fix/build_test.go new file mode 100644 index 0000000..c1b8768 --- /dev/null +++ b/internal/fix/build_test.go
@@ -0,0 +1,660 @@ +// 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 fix + +import ( + "testing" +) + +func TestBuild(t *testing.T) { + tests := []test{{ + desc: "proto2: empty composite literal", + in: ` +msg := &pb2.M2{} +msg = &pb2.M2{} +msg.Ms = []*pb2.M2{{},{}} +msg = func() *pb2.M2 { + return &pb2.M2{} +}() +func(*pb2.M2) { }(&pb2.M2{}) +_=msg +`, + want: map[Level]string{ + Green: ` +msg := &pb2.M2{} +msg = &pb2.M2{} +msg.SetMs([]*pb2.M2{{}, {}}) +msg = func() *pb2.M2 { + return &pb2.M2{} +}() +func(*pb2.M2) {}(&pb2.M2{}) +_ = msg +`, + }, + }, { + desc: "proto2: non-empty composite literal", + in: ` +msg := &pb2.M2{S:nil} +msg = &pb2.M2{S:nil} +msg.Ms = []*pb2.M2{{S:nil},{S:nil}} +msg = func() *pb2.M2 { + return &pb2.M2{S:nil} +}() +func(*pb2.M2) {}(&pb2.M2{S:nil}) +_ = msg +`, + want: map[Level]string{ + Green: ` +msg := pb2.M2_builder{S: nil}.Build() +msg = pb2.M2_builder{S: nil}.Build() +msg.SetMs([]*pb2.M2{pb2.M2_builder{S: nil}.Build(), pb2.M2_builder{S: nil}.Build()}) +msg = func() *pb2.M2 { + return pb2.M2_builder{S: nil}.Build() +}() +func(*pb2.M2) {}(pb2.M2_builder{S: nil}.Build()) +_ = msg +`, + }, + }, { + desc: "proto3: empty composite literal", + in: ` +msg := &pb3.M3{} +msg = &pb3.M3{} +msg.Ms = []*pb3.M3{{},{}} +msg = func() *pb3.M3 { + return &pb3.M3{} +}() +func(*pb3.M3) { }(&pb3.M3{}) +_=msg +`, + want: map[Level]string{ + Green: ` +msg := &pb3.M3{} +msg = &pb3.M3{} +msg.SetMs([]*pb3.M3{{}, {}}) +msg = func() *pb3.M3 { + return &pb3.M3{} +}() +func(*pb3.M3) {}(&pb3.M3{}) +_ = msg +`, + }, + }, { + desc: "proto3: non-empty composite literal", + in: ` +msg := &pb3.M3{M:nil} +msg = &pb3.M3{M:nil} +msg.Ms = []*pb3.M3{{M:nil},{M:nil}} +msg = func() *pb3.M3 { + return &pb3.M3{M:nil} +}() +func(*pb3.M3) { }(&pb3.M3{M:nil}) +_=msg +`, + want: map[Level]string{ + Green: ` +msg := pb3.M3_builder{M: nil}.Build() +msg = pb3.M3_builder{M: nil}.Build() +msg.SetMs([]*pb3.M3{pb3.M3_builder{M: nil}.Build(), pb3.M3_builder{M: nil}.Build()}) +msg = func() *pb3.M3 { + return pb3.M3_builder{M: nil}.Build() +}() +func(*pb3.M3) {}(pb3.M3_builder{M: nil}.Build()) +_ = msg +`, + }, + }, { + desc: "builder naming conflict", + in: ` +_ = &pb2.M2{ + Build: proto.Int32(1), +} +_ = []*pb2.M2{{Build: proto.Int32(1)}} +_ = map[int]*pb2.M2{ + 0: {Build: proto.Int32(1)}, +} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + Build_: proto.Int32(1), +}.Build() +_ = []*pb2.M2{pb2.M2_builder{Build_: proto.Int32(1)}.Build()} +_ = map[int]*pb2.M2{ + 0: pb2.M2_builder{Build_: proto.Int32(1)}.Build(), +} +`, + }, + }, { + desc: "proto2: scalars", + in: ` +_ = &pb2.M2{ + B: proto.Bool(true), + F32: proto.Float32(1), + F64: proto.Float64(1), + I32: proto.Int32(1), + I64: proto.Int64(1), + Ui32: proto.Uint32(1), + Ui64: proto.Uint64(1), + S: proto.String("hello"), + E: pb2.M2_E_VAL.Enum(), +} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + B: proto.Bool(true), + F32: proto.Float32(1), + F64: proto.Float64(1), + I32: proto.Int32(1), + I64: proto.Int64(1), + Ui32: proto.Uint32(1), + Ui64: proto.Uint64(1), + S: proto.String("hello"), + E: pb2.M2_E_VAL.Enum(), +}.Build() +`, + }, + }, { + desc: "proto3: scalars", + in: `_ = &pb3.M3{ + B: true, + F32: 1, + F64: 1, + I32: 1, + I64: 1, + Ui32: 1, + Ui64: 1, + S: "hello", +} +`, + want: map[Level]string{ + Green: ` +_ = pb3.M3_builder{ + B: true, + F32: 1, + F64: 1, + I32: 1, + I64: 1, + Ui32: 1, + Ui64: 1, + S: "hello", +}.Build() +`, + }, + }, { + desc: "scalar slices", + in: ` +_ = &pb2.M2{Is: []int32{1, 2, 3}} +_ = &pb3.M3{Is: []int32{1, 2, 3}} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{Is: []int32{1, 2, 3}}.Build() +_ = pb3.M3_builder{Is: []int32{1, 2, 3}}.Build() +`, + }, + }, { + desc: "msg slices", + in: ` +_ = &pb2.M2{Ms: []*pb2.M2{{}, &pb2.M2{}}} +_ = &pb2.M2{Ms: []*pb2.M2{{M: nil}, {M: nil}}} +_ = &pb3.M3{Ms: []*pb3.M3{{}, &pb3.M3{}}} +_ = &pb3.M3{Ms: []*pb3.M3{{M: nil}, {M: nil}}} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{Ms: []*pb2.M2{{}, &pb2.M2{}}}.Build() +_ = pb2.M2_builder{Ms: []*pb2.M2{pb2.M2_builder{M: nil}.Build(), pb2.M2_builder{M: nil}.Build()}}.Build() +_ = pb3.M3_builder{Ms: []*pb3.M3{{}, &pb3.M3{}}}.Build() +_ = pb3.M3_builder{Ms: []*pb3.M3{pb3.M3_builder{M: nil}.Build(), pb3.M3_builder{M: nil}.Build()}}.Build() +`, + }, + }, { + desc: "key-value builders", + in: ` +_ = map[int]*pb2.M2{ + 1: {}, + 2: {M:nil}, + 3: &pb2.M2{}, + 4: &pb2.M2{M: nil}, + 5: &pb2.M2{ + Ms: []*pb2.M2{{},{M:nil},&pb2.M2{},&pb2.M2{M:nil}}, + }, +} +_ = [...]*pb2.M2 { + 1: {}, + 2: {M:nil}, + 3: &pb2.M2{}, + 4: &pb2.M2{M: nil}, + 5: &pb2.M2{ + Ms: []*pb2.M2{{},{M:nil},&pb2.M2{},&pb2.M2{M:nil}}, + }, +} +`, + want: map[Level]string{ + Green: ` +_ = map[int]*pb2.M2{ + 1: {}, + 2: pb2.M2_builder{M: nil}.Build(), + 3: &pb2.M2{}, + 4: pb2.M2_builder{M: nil}.Build(), + 5: pb2.M2_builder{ + Ms: []*pb2.M2{{}, pb2.M2_builder{M: nil}.Build(), &pb2.M2{}, pb2.M2_builder{M: nil}.Build()}, + }.Build(), +} +_ = [...]*pb2.M2{ + 1: {}, + 2: pb2.M2_builder{M: nil}.Build(), + 3: &pb2.M2{}, + 4: pb2.M2_builder{M: nil}.Build(), + 5: pb2.M2_builder{ + Ms: []*pb2.M2{{}, pb2.M2_builder{M: nil}.Build(), &pb2.M2{}, pb2.M2_builder{M: nil}.Build()}, + }.Build(), +} +`, + }, + }, { + desc: "composite lit msg in slice", + in: ` +_ = []*pb2.M2{ + &pb2.M2{S: nil}, +} +`, + want: map[Level]string{ + Green: ` +_ = []*pb2.M2{ + pb2.M2_builder{S: nil}.Build(), +} +`, + }, + }, { + desc: "composite lit msg single line in slice", + in: ` +_ = []*pb2.M2{ &pb2.M2{S: nil} } +`, + want: map[Level]string{ + Green: ` +_ = []*pb2.M2{pb2.M2_builder{S: nil}.Build()} +`, + }, + }, { + desc: "multi-line msg slices", + in: ` +_ = []*pb2.M2{ + &pb2.M2{}, + &pb2.M2{M: nil}, + &pb2.M2{ + M: nil, + }, +} +_ = []*pb3.M3{ + {}, + {B: true}, + { + S: "hello", + }, +} +`, + want: map[Level]string{ + Green: ` +_ = []*pb2.M2{ + &pb2.M2{}, + pb2.M2_builder{M: nil}.Build(), + pb2.M2_builder{ + M: nil, + }.Build(), +} +_ = []*pb3.M3{ + {}, + pb3.M3_builder{B: true}.Build(), + pb3.M3_builder{ + S: "hello", + }.Build(), +} +`, + }, + }, { + desc: "oneofs: easy cases", + in: ` +_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{"hello"}} +_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{StringOneof: "hello"}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{[]byte("hello")}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte("hello")}} +_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{pb2.M2_E_VAL}} +_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{EnumOneof: pb2.M2_E_VAL}} +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{&pb2.M2{}}} +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{}}} +_ = &pb3.M3{OneofField: &pb3.M3_StringOneof{"hello"}} +_ = &pb3.M3{OneofField: &pb3.M3_StringOneof{StringOneof: "hello"}} +_ = &pb3.M3{OneofField: &pb3.M3_BytesOneof{[]byte("hello")}} +_ = &pb3.M3{OneofField: &pb3.M3_BytesOneof{BytesOneof: []byte("hello")}} +_ = &pb3.M3{OneofField: &pb3.M3_EnumOneof{pb3.M3_E_VAL}} +_ = &pb3.M3{OneofField: &pb3.M3_EnumOneof{EnumOneof: pb3.M3_E_VAL}} +_ = &pb3.M3{OneofField: &pb3.M3_MsgOneof{&pb3.M3{}}} +_ = &pb3.M3{OneofField: &pb3.M3_MsgOneof{MsgOneof: &pb3.M3{}}} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{StringOneof: proto.String("hello")}.Build() +_ = pb2.M2_builder{StringOneof: proto.String("hello")}.Build() +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = pb2.M2_builder{EnumOneof: pb2.M2_E_VAL.Enum()}.Build() +_ = pb2.M2_builder{EnumOneof: pb2.M2_E_VAL.Enum()}.Build() +_ = pb2.M2_builder{MsgOneof: &pb2.M2{}}.Build() +_ = pb2.M2_builder{MsgOneof: &pb2.M2{}}.Build() +_ = pb3.M3_builder{StringOneof: proto.String("hello")}.Build() +_ = pb3.M3_builder{StringOneof: proto.String("hello")}.Build() +_ = pb3.M3_builder{BytesOneof: []byte("hello")}.Build() +_ = pb3.M3_builder{BytesOneof: []byte("hello")}.Build() +_ = pb3.M3_builder{EnumOneof: pb3.M3_E_VAL.Enum()}.Build() +_ = pb3.M3_builder{EnumOneof: pb3.M3_E_VAL.Enum()}.Build() +_ = pb3.M3_builder{MsgOneof: &pb3.M3{}}.Build() +_ = pb3.M3_builder{MsgOneof: &pb3.M3{}}.Build() +`, + }, + }, { + desc: "oneofs: messages", + in: ` +_ = &pb2.M2{ + OneofField: &pb2.M2_MsgOneof{ + MsgOneof: &pb2.M2{M: nil}, + }, +} + +_ = &pb2.M2{ + OneofField: &pb2.M2_MsgOneof{&pb2.M2{M: nil}}, +} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + MsgOneof: pb2.M2_builder{M: nil}.Build(), +}.Build() + +_ = pb2.M2_builder{ + MsgOneof: pb2.M2_builder{M: nil}.Build(), +}.Build() +`, + }, + }, { + desc: "oneofs: literal int enums", + in: ` +_ = &pb3.M3{ + OneofField: &pb3.M3_EnumOneof{42}, +} +`, + want: map[Level]string{ + Green: ` +_ = &pb3.M3{ + OneofField: &pb3.M3_EnumOneof{42}, +} +`, + }, + }, { + desc: "oneof: basic-type zero value", + in: ` +_ = &pb2.M2{OneofField: &pb2.M2_StringOneof{}} +_ = &pb2.M2{OneofField: &pb2.M2_IntOneof{}} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{StringOneof: proto.String("")}.Build() +_ = pb2.M2_builder{IntOneof: proto.Int64(0)}.Build() +`, + }, + }, { + desc: "oneofs: nested messages", + extra: `var mOuter2 pb2.M2Outer`, + in: ` +_ = &pb2.M2Outer{ + OuterOneof: mOuter2.OuterOneof, + } + +_ = &pb2.M2Outer{ + OuterOneof: &pb2.M2Outer_InnerMsg{ + InnerMsg: &pb2.M2Outer_MInner{ + InnerOneof: &pb2.M2Outer_MInner_StringInner{ + StringInner: "Hello World!", + }, + }, + }, + } +`, + want: map[Level]string{ + Red: ` +_ = pb2.M2Outer_builder{ + InnerMsg: mOuter2.GetInnerMsg(), + StringOneof: proto.ValueOrNil(mOuter2.HasStringOneof(), mOuter2.GetStringOneof), +}.Build() + +_ = pb2.M2Outer_builder{ + InnerMsg: pb2.M2Outer_MInner_builder{ + StringInner: proto.String("Hello World!"), + }.Build(), +}.Build() +`, + }, + }, { + desc: "oneofs: preserve comments", + in: ` +_ = &pb2.M2{ + // comment before + OneofField: /*comment1*/ m2.GetOneofField(), // eol comment + } +`, + want: map[Level]string{ + Yellow: ` +_ = pb2.M2_builder{ + // comment before + BytesOneof:/*comment1*/ m2.GetBytesOneof(), // eol comment + EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof), + IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof), + MsgOneof: m2.GetMsgOneof(), + StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof), +}.Build() +`, + Red: ` +_ = pb2.M2_builder{ + // comment before + BytesOneof:/*comment1*/ m2.GetBytesOneof(), // eol comment + EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof), + IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof), + MsgOneof: m2.GetMsgOneof(), + StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof), +}.Build() +`, + }, + }, { + desc: "oneofs: preserve comments when using setters", + srcfiles: []string{"pkg.go"}, + in: ` +_ = &pb2.M2{ + // comment before + OneofField: /*comment1*/ &pb2.M2_StringOneof{"hello"}, // eol comment + } +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +// comment before +m2h2.SetStringOneof("hello") // eol comment +_ = m2h2 +`, + }, + }, { + + desc: "oneofs: propagate oneof", + extra: `func F() *pb2.M2_MsgOneof {return nil}`, + in: ` +var scalarOneof *pb2.M2_StringOneof +_ = &pb2.M2{OneofField: scalarOneof} + +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} + +_ = &pb2.M2{OneofField: F()} + +ifaceOneof := m2.GetOneofField() +_ = &pb2.M2{OneofField: ifaceOneof} + +_ = &pb2.M2{ + OneofField: m2.GetOneofField(), + S: proto.String("42"), + } +`, + want: map[Level]string{ + Yellow: ` +var scalarOneof *pb2.M2_StringOneof +_ = &pb2.M2{OneofField: scalarOneof} + +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} + +_ = &pb2.M2{OneofField: F()} + +ifaceOneof := m2.GetOneofField() +_ = &pb2.M2{OneofField: ifaceOneof} + +_ = pb2.M2_builder{ + S: proto.String("42"), + BytesOneof: m2.GetBytesOneof(), + EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof), + IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof), + MsgOneof: m2.GetMsgOneof(), + StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof), +}.Build() +`, + Red: ` +var scalarOneof *pb2.M2_StringOneof +_ = pb2.M2_builder{StringOneof: proto.String(scalarOneof.StringOneof)}.Build() + +var msgOneof *pb2.M2_MsgOneof +_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msgOneof.MsgOneof)}.Build() + +_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(F().MsgOneof)}.Build() + +ifaceOneof := m2.GetOneofField() +_ = pb2.M2_builder{OneofField: ifaceOneof}.Build() + +_ = pb2.M2_builder{ + S: proto.String("42"), + BytesOneof: m2.GetBytesOneof(), + EnumOneof: proto.ValueOrNil(m2.HasEnumOneof(), m2.GetEnumOneof), + IntOneof: proto.ValueOrNil(m2.HasIntOneof(), m2.GetIntOneof), + MsgOneof: m2.GetMsgOneof(), + StringOneof: proto.ValueOrNil(m2.HasStringOneof(), m2.GetStringOneof), +}.Build() +`, + }, + }, { + desc: "oneofs: potentially nil message", + in: ` +msg := &pb2.M2{} +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}} +`, + want: map[Level]string{ + Green: ` +msg := &pb2.M2{} +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}} +`, + Red: ` +msg := &pb2.M2{} +_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msg)}.Build() +`, + }, + }, { + desc: "oneofs: msg zero value", + in: ` +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{}} +`, + want: map[Level]string{ + Green: ` +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{}} +`, + Red: ` +_ = pb2.M2_builder{OneofField: &pb2.M2_MsgOneof{}}.Build() +`, + }, + }, { + desc: "oneofs: enum zero value", // no rewrite (see comments in rules.go) + in: ` +_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{}} +`, + want: map[Level]string{ + Green: ` +_ = &pb2.M2{OneofField: &pb2.M2_EnumOneof{}} +`, + Red: ` +_ = pb2.M2_builder{OneofField: &pb2.M2_EnumOneof{}}.Build() +`, + }, + }, { + desc: "oneofs: oneofs builder rewrite", + extra: `func F() *pb2.M2_MsgOneof2 {return nil}`, + in: ` +msg := &pb2.M2{} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_EnumOneof2{}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_MsgOneof2{msg}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_MsgOneof2{}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: F(), +} +`, + want: map[Level]string{ + Green: ` +msg := &pb2.M2{} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_EnumOneof2{}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_MsgOneof2{msg}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_MsgOneof2{}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: F(), +} +`, + Yellow: ` +msg := &pb2.M2{} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_EnumOneof2{}, +} +_ = pb2.M2_builder{ + StringOneof: proto.String("hello"), + MsgOneof2: protooneofdefault.ValueOrDefault(msg), +}.Build() +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: &pb2.M2_MsgOneof2{}, +} +_ = &pb2.M2{ + OneofField: &pb2.M2_StringOneof{"hello"}, + OneofField2: F(), +} +`, + }, + }} + + runTableTests(t, tests) +}
diff --git a/internal/fix/clone.go b/internal/fix/clone.go new file mode 100644 index 0000000..9f54522 --- /dev/null +++ b/internal/fix/clone.go
@@ -0,0 +1,176 @@ +// 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 fix + +import ( + "fmt" + "reflect" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +func cloneSelectorExpr(c *cursor, src *dst.SelectorExpr) *dst.SelectorExpr { + out := dst.Clone(src).(*dst.SelectorExpr) + + // Match type information for all subexpressions to corresponding type + // information from the source. + // + // Call c.setType for all dst.Expr. + // Call c.setUse for all *dst.Ident. + var cloneTypes func(from, to reflect.Value) + cloneTypes = func(from, to reflect.Value) { + if !from.IsValid() || !to.IsValid() || from.IsZero() || to.IsZero() || isNil(from) || isNil(to) { + return + } + // from and to are clones so we assume that they are structurally + // similar. Unfortunately, they are not actually the same as dst.Clone + // loses (for example) information about Object references. + if from.Kind() != to.Kind() { // A cheap sanity check. + panic(fmt.Sprintf("bad kind %s vs %s (%+v vs %+v)", from.Kind(), to.Kind(), from, to)) + } + switch from.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < from.Len(); i++ { + cloneTypes(from.Index(i), to.Index(i)) + } + case reflect.Interface: + if from.IsNil() { + return + } + cloneTypes(from.Elem(), to.Elem()) + case reflect.Map: + // The DST package doesn't use maps for anything significant here. + panic("map is not supported") + case reflect.Ptr: + if from.IsNil() { + return + } + cloneTypes(from.Elem(), to.Elem()) + case reflect.Struct: + for i := 0; i < from.NumField(); i++ { + cloneTypes(from.Field(i), to.Field(i)) + } + } + if from.Type().AssignableTo(dstExprType) && from.Kind() != reflect.Interface { + c.setType(to.Interface().(dst.Expr), c.typeOf(from.Interface().(dst.Expr))) + } + if from.Type() == dstIdentType { + // If the source identifier has an unknown use then don't try to + // propagate it to the new identifier. This could happen for manually + // created dst.Ident and we are currently really bad at maintaining + // that use information. + if use := c.objectOf(from.Interface().(*dst.Ident)); use != nil { + c.setUse(to.Interface().(*dst.Ident), use) + } + } + } + cloneTypes(reflect.ValueOf(src), reflect.ValueOf(out)) + updateAstMapTree(c, src, out) + return out +} + +func cloneIdent(c *cursor, ident *dst.Ident) *dst.Ident { + out := &dst.Ident{ + Name: ident.Name, + Path: ident.Path, + } + c.setType(out, c.typeOf(ident)) + if use := c.objectOf(ident); use != nil { + c.setUse(out, use) + } + updateASTMap(c, ident, out) + return out +} + +// cloneSelectorCallExpr clones call expression of the form "m.F()" +func cloneSelectorCallExpr(c *cursor, src *dst.CallExpr) *dst.CallExpr { + sel, ok := src.Fun.(*dst.SelectorExpr) + if !ok { + panic("invalid cloneSelectorCallExpr call: src.Fun must be a selector") + } + call := &dst.CallExpr{ + Fun: cloneSelectorExpr(c, sel), + } + c.setType(call, c.typeOf(src)) + return call +} + +// cloneIndexExpr clones call expression of the form "m.F()" +func cloneIndexExpr(c *cursor, src *dst.IndexExpr) *dst.IndexExpr { + idx := &dst.IndexExpr{ + X: cloneExpr(c, src.X), + Index: cloneExpr(c, src.Index), + } + c.setType(idx.X, c.typeOf(src.X)) + c.setType(idx.Index, c.typeOf(src.Index)) + c.setType(idx, c.typeOf(src)) + return idx +} + +// cloneIndexExpr clones call expression of the form "m.F()" +func cloneBasicLit(c *cursor, src *dst.BasicLit) *dst.BasicLit { + bl := &dst.BasicLit{ + Kind: src.Kind, + Value: src.Value, + } + c.setType(bl, c.typeOf(src)) + return bl +} + +// cloneExpr dispatches to any of the functions above. +func cloneExpr(c *cursor, src dst.Expr) dst.Expr { + switch v := src.(type) { + case *dst.Ident: + return cloneIdent(c, v) + case *dst.IndexExpr: + return cloneIndexExpr(c, v) + case *dst.BasicLit: + return cloneBasicLit(c, v) + case *dst.CallExpr: + return cloneSelectorCallExpr(c, v) + case *dst.SelectorExpr: + return cloneSelectorExpr(c, v) + default: + panic(fmt.Sprintf("unhandled type for cloneExpr %T", src)) + } +} + +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + default: + return false + } +} + +// updateAstMapTree updates a the astMap for a cloned dst tree. This function +// assumes that source and copy are exact copies of each other. After this +// function each node in copy will be mapped to the same ast node as the +// associated node in source. +func updateAstMapTree(c *cursor, source, copy dst.Node) { + var nodes []dst.Node + dstutil.Apply(source, func(cur *dstutil.Cursor) bool { + nodes = append(nodes, cur.Node()) + return true + }, nil) + cnt := 0 + dstutil.Apply(copy, func(cur *dstutil.Cursor) bool { + updateASTMap(c, nodes[cnt], cur.Node()) + cnt++ + return true + }, nil) +} + +func updateASTMap(c *cursor, source dst.Node, copy dst.Node) { + // Retain the mapping between dave/dst and go/ast nodes. + // This is necessary for looking up position information. + // The cloned ident will not be at the same position as + // its source, but it will be close enough for our purposes. + if n, ok := c.typesInfo.astMap[source]; ok { + c.typesInfo.astMap[copy] = n + } +}
diff --git a/internal/fix/conflictingname.go b/internal/fix/conflictingname.go new file mode 100644 index 0000000..c0cae08 --- /dev/null +++ b/internal/fix/conflictingname.go
@@ -0,0 +1,53 @@ +// 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 fix + +import ( + "go/types" + "strings" +) + +func fixConflictingNames(t types.Type, prefix, name string) string { + if prefix != "" { + if pt, ok := t.(*types.Pointer); ok { + t = pt.Elem() + } + st := t.(*types.Named).Underlying().(*types.Struct) + for i := 0; i < st.NumFields(); i++ { + if st.Field(i).Name() == prefix+name { + return "_" + name + } + } + } + // A "build" field in the protocol buffer results in a "Build_" field in + // the builder (the "_" is added to avoid conflicts with the "Build" + // method). As a result, the generator renames all "Build" accessor + // methods in the message (?!). + if name == "Build" { + name = "Build_" + } + // Before the migration, the following names conflicted with methods on + // the message struct. The corresponding accessor methods are deprecated + // and we should use versions without the "_" suffix. + for _, s := range []string{"Reset_", "String_", "ProtoMessage_", "Descriptor_"} { + if name == s { + name = strings.TrimSuffix(name, "_") + } + } + return name +} + +// See usedNames in +// https://go.googlesource.com/protobuf/+/refs/heads/master/compiler/protogen/protogen.go +var conflictingNames = map[string]bool{ + "Reset": true, + "String": true, + "ProtoMessage": true, + "Marshal": true, + "Unmarshal": true, + "ExtensionRangeArray": true, + "ExtensionMap": true, + "Descriptor": true, +}
diff --git a/internal/fix/conflictingname_test.go b/internal/fix/conflictingname_test.go new file mode 100644 index 0000000..22ed204 --- /dev/null +++ b/internal/fix/conflictingname_test.go
@@ -0,0 +1,139 @@ +// 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 fix + +import ( + "testing" +) + +func TestConflictingName(t *testing.T) { + tests := []test{ + { + desc: "resolve conflict with Build", + in: ` +_ = &pb2.M2{ + Build: proto.Int32(1), +} + +_ = *m2.Build +_ = m2.Build != nil + +m2.Build = proto.Int32(1) +m2.Build = nil +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + Build_: proto.Int32(1), +}.Build() + +_ = m2.GetBuild_() +_ = m2.HasBuild_() + +m2.SetBuild_(1) +m2.ClearBuild_() +`, + }, + }, + + { + desc: "resolve conflict with message methods", + in: ` +_ = &pb2.M2{ + ProtoMessage_: proto.Int32(1), + Reset_: proto.Int32(1), + String_: proto.Int32(1), + Descriptor_: proto.Int32(1), +} + +_ = *m2.ProtoMessage_ +_ = m2.ProtoMessage_ != nil +m2.ProtoMessage_ = nil +m2.ProtoMessage_ = proto.Int32(1) + +_ = *m2.Reset_ +_ = m2.Reset_ != nil +m2.Reset_ = nil +m2.Reset_ = proto.Int32(1) + +_ = *m2.String_ +_ = m2.String_ != nil +m2.String_ = nil +m2.String_ = proto.Int32(1) + +_ = *m2.Descriptor_ +_ = m2.Descriptor_ != nil +m2.Descriptor_ = nil +m2.Descriptor_ = proto.Int32(1) +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + ProtoMessage: proto.Int32(1), + Reset: proto.Int32(1), + String: proto.Int32(1), + Descriptor: proto.Int32(1), +}.Build() + +_ = m2.GetProtoMessage() +_ = m2.HasProtoMessage() +m2.ClearProtoMessage() +m2.SetProtoMessage(1) + +_ = m2.GetReset() +_ = m2.HasReset() +m2.ClearReset() +m2.SetReset(1) + +_ = m2.GetString() +_ = m2.HasString() +m2.ClearString() +m2.SetString(1) + +_ = m2.GetDescriptor() +_ = m2.HasDescriptor() +m2.ClearDescriptor() +m2.SetDescriptor(1) +`, + }, + }, + + { + desc: "resolve conflict with message methods due to oneofs", + in: ` +_ = &pb3.M3{ + OneofField: &pb3.M3_ProtoMessage_{""}, +} +_ = &pb3.M3{ + OneofField: &pb3.M3_Reset_{""}, +} +_ = &pb3.M3{ + OneofField: &pb3.M3_String_{""}, +} +_ = &pb3.M3{ + OneofField: &pb3.M3_Descriptor_{""}, +} +`, + want: map[Level]string{ + Green: ` +_ = pb3.M3_builder{ + ProtoMessage: proto.String(""), +}.Build() +_ = pb3.M3_builder{ + Reset: proto.String(""), +}.Build() +_ = pb3.M3_builder{ + String: proto.String(""), +}.Build() +_ = pb3.M3_builder{ + Descriptor: proto.String(""), +}.Build() +`, + }, + }, + } + + runTableTests(t, tests) +}
diff --git a/internal/fix/converttosetter.go b/internal/fix/converttosetter.go new file mode 100644 index 0000000..bc81283 --- /dev/null +++ b/internal/fix/converttosetter.go
@@ -0,0 +1,374 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +// convertToSetterPost rewrites all non-empty composite literals into setters. +// For example: +// +// m := &pb.M{ +// StringField: proto.String("Hello"), +// } +// +// => +// +// m := &pb.M{} +// m.SetStringField("Hello") +// +// That is, we DO NOT do the following: +// +// m := pb.M_builder{ +// StringField: proto.String("Hello"), +// }.Build() +// +// We use setters instead of builders to avoid performance regressions, +// see the Opaque API FAQ: https://protobuf.dev/reference/go/opaque-faq/ +func convertToSetterPost(c *cursor) bool { + n := c.Node() + // We need to work on the statement level, because we need to insert a new + // statement (new variable declaration). + if _, ok := n.(dst.Stmt); !ok { + return true + } + + // Ensure that the current statement is part of a slice so that we can + // prepend additional statements using c.InsertBefore(). + switch p := c.Parent().(type) { + case *dst.BlockStmt: + // This statement is part of the BlockStmt.List slice + case *dst.CaseClause: + // This statement is part of the CaseClause.Body slice + case *dst.CommClause: + if p.Comm == n { + return true + } + // This statement is part of the CommClause.Body slice + default: + return true + } + + var assignmentReused *dst.AssignStmt + + var labelName *dst.Ident + if ls, ok := n.(*dst.LabeledStmt); ok { + labelName = ls.Label + } + + // Find proto struct literals within the current statement and extract them. + dstutil.Apply(n, + nil, + func(cur *dstutil.Cursor) bool { + // Extract the builder composite literal into a helper variable and + // modify it by calling the corresponding setter functions. + lit, ok := c.builderCLit(cur.Node(), cur.Parent()) + if !ok { + return true + } + if c.useBuilder(lit) { + c.Logf("requested to use builders for this file or type %v", c.typeOf(lit)) + return true + } + + // builderCLit returning ok means cur.Node() is a UnaryExpr (&{…}) + // or a CompositeLit ({…}), both of which implement Expr. + litExpr := cur.Node().(dst.Expr) + + var exName string // name of the extracted part of the literal + var exSource *dst.Ident + shallowCopies := 0 + if as, ok := cur.Parent().(*dst.AssignStmt); ok { + // Only rewrite shallow copies in red level, because it requires + // follow-up changes to the code (e.g. changing the shallow copy + // to use proto.Merge()). + for _, lhs := range as.Lhs { + if _, ok := lhs.(*dst.StarExpr); ok { + if c.lvl.le(Yellow) { + c.Logf("shallow copy detected, skipping") + return true + } + shallowCopies++ + } + } + } + if as, ok := n.(*dst.AssignStmt); ok && n == cur.Parent() { + // For assignments (result := &pb.M2{…}) we reuse the variable + // name (result) instead of introducing a helper only to + // ultimately assign result := helper. + for idx, rhs := range as.Rhs { + if rhs != cur.Node() { + continue + } + if as.Tok == token.ASSIGN && !types.Identical(c.typeOf(as.Lhs[idx]), c.typeOf(rhs)) { + // If the static type is different then we might not be able + // to call methods on it, e.g.: + // + // var myMsg proto.Message + // myMsg = &pb2.M2{S: proto.String("Hello")} + // + // If we translate this as is, it would fail type checking: + // + // var myMsg proto.Message + // myMsg = &pb2.M2{} + // myMsg.SetS("Hello") // compile-time error + break + } + if id, ok := as.Lhs[idx].(*dst.Ident); ok && id.Name != "_" { + exSource = id + exName = id.Name + assignmentReused = as + } + } + } + if exName == "" { + exName = c.helperNameFor(c.Node(), c.typeOf(litExpr)) + } + + qualifiedLitExpr := litExpr + if cl, ok := qualifiedLitExpr.(*dst.CompositeLit); ok { + // The expression is a CompositeLit without explicit type, which + // is valid within a slice initializer, but not outside, so we + // need to wrap it in a UnaryExpr and assign a type identifier. + typ := c.typeOf(litExpr) + qualifiedLitExpr = &dst.UnaryExpr{ + Op: token.AND, + X: litExpr, + } + updateASTMap(c, litExpr, qualifiedLitExpr) + c.setType(qualifiedLitExpr, typ) + cl.Type = c.selectorForProtoMessageType(typ) + } + + exIdent := &dst.Ident{Name: exName} + if exSource != nil { + updateASTMap(c, exSource, exIdent) + } else { + updateASTMap(c, lit, exIdent) + } + c.setType(exIdent, c.typeOf(qualifiedLitExpr)) + tok := token.DEFINE + if assignmentReused != nil { + tok = assignmentReused.Tok + } + assign := (dst.Stmt)(&dst.AssignStmt{ + Lhs: []dst.Expr{exIdent}, + Tok: tok, + Rhs: []dst.Expr{qualifiedLitExpr}, + }) + + replacement := cloneIdent(c, exIdent) + // Move line break decorations from the literal to its replacement. + replacement.Decorations().Before = litExpr.Decorations().Before + replacement.Decorations().After = litExpr.Decorations().After + + if ce, ok := cur.Parent().(*dst.CallExpr); ok { + for idx, arg := range ce.Args { + if arg != cur.Node() { + continue + } + if !fitsOnSameLine(ce, replacement) { + continue + } + replacement.Decorations().Before = dst.None + replacement.Decorations().After = dst.None + if idx == 0 { + continue + } + previous := ce.Args[idx-1] + previous.Decorations().After = dst.None + } + } + + // Move end-of-line comments from the literal to its replacement. + replacement.Decorations().End = litExpr.Decorations().End + + // Move decorations (line break and comments) of the containing + // statement to the first inserted helper variable. + assign.Decorations().Before = n.Decorations().Before + assign.Decorations().Start = n.Decorations().Start + n.Decorations().Before = dst.None + n.Decorations().Start = nil + // Move decorations (line break and comments) of the literal to its + // corresponding helper variable. + if assign.Decorations().Before == dst.None { + assign.Decorations().Before = litExpr.Decorations().Before + } + assign.Decorations().Start = append(assign.Decorations().Start, litExpr.Decorations().Start...) + + // Remove all decorations from the composite literal to ensure that + // there is no line break within the new AssignStmt (would fail to + // compile). Comments have been retained in the lines above. + litExpr.Decorations().Before = dst.None + litExpr.Decorations().After = dst.None + litExpr.Decorations().Start = nil + litExpr.Decorations().End = nil + + // If the current node is a LabeledStmt, move the label to the first + // inserted helper variable assignment. + if labelName != nil { + // Turn a Stmt with a label into just the Stmt, without a label. + c.Replace(n.(*dst.LabeledStmt).Stmt) + // Add the label to the assignment we are inserting. + assign = &dst.LabeledStmt{ + Label: labelName, + Stmt: assign, + } + labelName = nil + } + + // Rewrite the composite literal to assignments. + elts := lit.Elts + lit.Elts = nil + // Replace references to exIdent.Name in the right-hand side: + // now that we have cleared the RHS, references to exIdent.Name + // refer to the new struct literal! b/277902682 + shadows := false + for _, e := range elts { + kv := e.(*dst.KeyValueExpr) + dstutil.Apply(kv.Value, + func(cur *dstutil.Cursor) bool { + if _, ok := cur.Node().(*dst.SelectorExpr); ok { + // skip over SelectorExprs to avoid false positives + // when part of the selector (e.g. cmd.zone) happens + // to match the identifier we are looking for + // (e.g. zone). + return false + } + + id, ok := cur.Node().(*dst.Ident) + if !ok { + return true + } + if id.Name == exIdent.Name { + shadows = true + } + return true + }, + nil) + } + if shadows { + // The right-hand side references exIdent. Introduce a helper + // variable (e.g. cri2 := cri) and update all RHS references to + // use the helper variable: + helperName := c.helperNameFor(cur.Node(), c.typeOf(litExpr)) + helperIdent := &dst.Ident{Name: helperName} + updateASTMap(c, lit, helperIdent) + c.setType(helperIdent, c.typeOf(litExpr)) + + helperAssign := &dst.AssignStmt{ + Lhs: []dst.Expr{helperIdent}, + Tok: token.DEFINE, + Rhs: []dst.Expr{cloneIdent(c, exIdent)}, + } + // Move decorations (line break and comments) from assign to + // helperAssign, which just became the first inserted variable. + helperAssign.Decorations().Before = assign.Decorations().Before + helperAssign.Decorations().Start = assign.Decorations().Start + assign.Decorations().Before = dst.None + assign.Decorations().Start = nil + c.InsertBefore(helperAssign) + + for _, e := range elts { + kv := e.(*dst.KeyValueExpr) + dstutil.Apply(kv.Value, + func(cur *dstutil.Cursor) bool { + if _, ok := cur.Node().(*dst.SelectorExpr); ok { + // skip over SelectorExprs to avoid false positives + // when part of the selector (e.g. cmd.zone) happens + // to match the identifier we are looking for + // (e.g. zone). + return false + } + + id, ok := cur.Node().(*dst.Ident) + if !ok { + return true + } + if id.Name == exIdent.Name { + id.Name = helperIdent.Name + } + return true + }, + nil) + } + } + c.InsertBefore(assign) + for _, e := range elts { + kv := e.(*dst.KeyValueExpr) + lhs := &dst.SelectorExpr{ + X: cloneIdent(c, exIdent), + Sel: cloneIdent(c, kv.Key.(*dst.Ident)), + } + c.setType(lhs, c.typeOf(lhs.Sel)) + a := &dst.AssignStmt{ + Lhs: []dst.Expr{lhs}, + Tok: token.ASSIGN, + Rhs: []dst.Expr{kv.Value}, + } + *a.Decorations() = *kv.Decorations() + c.InsertBefore(a) + } + + c.numUnsafeRewritesByReason[ShallowCopy] += shallowCopies + cur.Replace(replacement) + + return true + }) + + if assignmentReused != nil && isIdenticalAssignment(n.(*dst.AssignStmt)) { + c.Delete() + } + + return true +} + +func fitsOnSameLine(call *dst.CallExpr, replacement *dst.Ident) bool { + combinedLen := len(replacement.Name) + dstutil.Apply(call, + func(cur *dstutil.Cursor) bool { + if cur.Node() == call.Args[len(call.Args)-1] { + // Skip the last argument, because we are about to replace it. + return false // skip children + } + if id, ok := cur.Node().(*dst.Ident); ok { + combinedLen += len(id.Name) + } + return true + }, + nil) + return combinedLen < 80 +} + +// isIdenticalAssignment reports whether the provided assign statement has the +// same names on the left hand side and right hand side, e.g. “foo := foo” or +// “foo, bar := foo, bar”. This can happen as part of convertToSetter() and +// results in the deletion of the now-unnecessary assignment. +func isIdenticalAssignment(as *dst.AssignStmt) bool { + if len(as.Lhs) != len(as.Rhs) { + return false + } + + for idx := range as.Lhs { + lhs, ok := as.Lhs[idx].(*dst.Ident) + if !ok { + return false + } + rhs, ok := as.Rhs[idx].(*dst.Ident) + if !ok { + return false + } + if lhs.Name != rhs.Name { + return false + } + } + + return true +}
diff --git a/internal/fix/converttosetter_test.go b/internal/fix/converttosetter_test.go new file mode 100644 index 0000000..7ecb0b7 --- /dev/null +++ b/internal/fix/converttosetter_test.go
@@ -0,0 +1,884 @@ +// 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 fix + +import ( + "testing" +) + +func TestConvertToSetter(t *testing.T) { + tests := []test{ + { + desc: "assignment", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{ + I32: proto.Int32(23), + M: &pb2.M2{ + I32: proto.Int32(42), + }, +} +_ = mypb +for idx, val := range []int32{123, 456} { + idx, val := idx, val + go func() { println(idx, val) }() +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +mypb := &pb2.M2{} +mypb.SetI32(23) +mypb.SetM(m2h2) +_ = mypb +for idx, val := range []int32{123, 456} { + idx, val := idx, val + go func() { println(idx, val) }() +} +`, + }, + }, + + { + desc: "multi assignment", + srcfiles: []string{"pkg.go"}, + in: ` +my1, my2 := &pb2.M2{ + I32: proto.Int32(23), +}, &pb2.M2{ + I64: proto.Int64(12345), +} +_, _ = my1, my2 +`, + want: map[Level]string{ + Red: ` +my1 := &pb2.M2{} +my1.SetI32(23) +my2 := &pb2.M2{} +my2.SetI64(12345) +_, _ = my1, my2 +`, + }, + }, + + { + desc: "struct literal field", + srcfiles: []string{"pkg.go"}, + in: ` +func() *pb2.M2 { + return &pb2.M2{ + I32: proto.Int32(23), + } +}() +`, + want: map[Level]string{ + Red: ` +func() *pb2.M2 { + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + return m2h2 +}() +`, + }, + }, + + { + desc: "nested builder", + srcfiles: []string{"pkg.go"}, + in: ` +_ = &pb2.M2{ + I32: proto.Int32(23), + M: &pb2.M2{ + I32: proto.Int32(42), + }, + Is: []int32{10, 11}, +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +m2h3 := &pb2.M2{} +m2h3.SetI32(23) +m2h3.SetM(m2h2) +m2h3.SetIs([]int32{10, 11}) +_ = m2h3 +`, + }, + }, + + { + desc: "if conditional", + srcfiles: []string{"pkg.go"}, + extra: `func validateProto(msg *pb2.M2) bool { return false }`, + in: ` +if msg := (&pb2.M2{ + I32: proto.Int32(23), + M: &pb2.M2{ + I32: proto.Int32(42), + }, + Is: []int32{10, 11}, +}); validateProto(msg) { + println("validated") +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +m2h3 := &pb2.M2{} +m2h3.SetI32(23) +m2h3.SetM(m2h2) +m2h3.SetIs([]int32{10, 11}) +if msg := (m2h3); validateProto(msg) { + println("validated") +} +`, + }, + }, + + { + desc: "case clause", + srcfiles: []string{"pkg.go"}, + extra: `func validateProto(msg *pb2.M2) bool { return false }`, + in: ` +switch { +case validateProto(nil): +_ = &pb2.M2{ + I32: proto.Int32(23), +} +} +`, + want: map[Level]string{ + Red: ` +switch { +case validateProto(nil): + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + _ = m2h2 +} +`, + }, + }, + + { + desc: "select clause", + srcfiles: []string{"pkg.go"}, + in: ` +dummy := make(chan bool) +select { +case <-dummy: +_ = &pb2.M2{ + I32: proto.Int32(23), +} +} +`, + want: map[Level]string{ + Red: ` +dummy := make(chan bool) +select { +case <-dummy: + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + _ = m2h2 +} +`, + }, + }, + + { + desc: "never nil byte expression", + srcfiles: []string{"pkg.go"}, + in: ` +var b []byte +m2.Bytes = b[2:3] +m2.Bytes = b[:3] +m2.Bytes = b[2:] +m2.Bytes = b[:] +m2.Bytes = b[0:] +m2.Bytes = b[:0] +m2.Bytes = b[0:0] +`, + want: map[Level]string{ + Green: ` +var b []byte +m2.SetBytes(b[2:3]) +m2.SetBytes(b[:3]) +m2.SetBytes(b[2:]) +if x := b[:]; x != nil { + m2.SetBytes(x) +} else { + m2.ClearBytes() +} +if x := b[0:]; x != nil { + m2.SetBytes(x) +} else { + m2.ClearBytes() +} +if x := b[:0]; x != nil { + m2.SetBytes(x) +} else { + m2.ClearBytes() +} +if x := b[0:0]; x != nil { + m2.SetBytes(x) +} else { + m2.ClearBytes() +} +`, + }, + }, + + { + desc: "variable declaration block", + srcfiles: []string{"pkg.go"}, + in: ` +var ( +_ = &pb2.M2{ + I32: proto.Int32(23), +} +) +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(23) +var ( + _ = m2h2 +) +`, + }, + }, + + { + desc: "defer statement", + srcfiles: []string{"pkg.go"}, + extra: `func validateProto(msg *pb2.M2) bool { return false }`, + in: ` +defer validateProto(&pb2.M2{ + I32: proto.Int32(23), +}) +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(23) +defer validateProto(m2h2) +`, + }, + }, + + { + desc: "go statement", + srcfiles: []string{"pkg.go"}, + extra: `func validateProto(msg *pb2.M2) bool { return false }`, + in: ` +go validateProto(&pb2.M2{ + I32: proto.Int32(23), +}) +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(23) +go validateProto(m2h2) +`, + }, + }, + + { + desc: "for loop", + srcfiles: []string{"pkg.go"}, + extra: `func validateProto(msg *pb2.M2) bool { return false }`, + in: ` +for m2 := (&pb2.M2{ + I32: proto.Int32(23), +}); m2 != nil; m2 = nil { +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(23) +for m2 := (m2h2); m2 != nil; m2 = nil { +} +`, + }, + }, + + { + desc: "range statement", + srcfiles: []string{"pkg.go"}, + in: ` +for _, idx := range []int{1, 2, 3} { + _ = idx + _ = &pb2.M2{ + I32: proto.Int32(23), + } +} +`, + want: map[Level]string{ + Red: ` +for _, idx := range []int{1, 2, 3} { + _ = idx + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + _ = m2h2 +} +`, + }, + }, + + { + desc: "labeled statement", + srcfiles: []string{"pkg.go"}, + in: ` +goto validate +println("this line is skipped") +validate: +println(&pb2.M2{ + I32: proto.Int32(23), +}) +`, + want: map[Level]string{ + Red: ` + goto validate + println("this line is skipped") +validate: + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + println(m2h2) +`, + }, + }, + + { + desc: "slice", + srcfiles: []string{"pkg.go"}, + in: ` +for _, msg := range []*pb2.M2{ + { + I32: proto.Int32(23), + }, + &pb2.M2{ + I64: proto.Int64(123), + }, +} { + println(msg) +} + `, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(23) +m2h3 := &pb2.M2{} +m2h3.SetI64(123) +for _, msg := range []*pb2.M2{ + m2h2, + m2h3, +} { + println(msg) +} +`, + }, + }, + + { + desc: "conditional assignment", + srcfiles: []string{"pkg.go"}, + in: ` +var mypb *pb2.M2 +if true { + mypb = &pb2.M2{ + I32: proto.Int32(23), + } +} +_ = mypb +`, + want: map[Level]string{ + Red: ` +var mypb *pb2.M2 +if true { + mypb = &pb2.M2{} + mypb.SetI32(23) +} +_ = mypb +`, + }, + }, + + { + desc: "assignment with different type", + srcfiles: []string{"pkg.go"}, + in: ` +var mypb proto.Message +if true { + mypb = &pb2.M2{ + I32: proto.Int32(23), + } +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +var mypb proto.Message +if true { + m2h2 := &pb2.M2{} + m2h2.SetI32(23) + mypb = m2h2 +} +_ = mypb +`, + }, + }, + + { + desc: "slice with comments", + srcfiles: []string{"pkg.go"}, + in: ` +for _, msg := range []*pb2.M2{ + // Comment above first literal + { + I32: proto.Int32(23), // End-of-line comment for I32 + }, + // Comment above second literal + &pb2.M2{ + // Comment above I64 + I64: proto.Int64(/* before 123 */ 123 /* after 123 */), + // Comment at the end of the second literal + }, // End-of-line comment after second literal +} { + println(msg) +} + `, + want: map[Level]string{ + Red: ` +// Comment above first literal +m2h2 := &pb2.M2{} +m2h2.SetI32(23) // End-of-line comment for I32 +// Comment above second literal +m2h3 := &pb2.M2{} +// Comment above I64 +m2h3.SetI64(123 /* after 123 */) +// Comment at the end of the second literal +for _, msg := range []*pb2.M2{ + m2h2, + m2h3, // End-of-line comment after second literal +} { + println(msg) +} +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestPointerDereference(t *testing.T) { + + tests := []test{ + { + desc: "assignment, lhs proto field dereference", + srcfiles: []string{"pkg.go"}, + in: ` +if val := m2.I32; val != nil { + *val = int32(42) +} +`, + want: map[Level]string{ + Red: ` +if m2.HasI32() { + m2.SetI32(int32(42)) +} +`, + }, + }, + + { + desc: "multi-assign, lhs proto field dereference", + srcfiles: []string{"pkg.go"}, + in: ` +var i int +if val := m2.I32; val != nil { + i, *val, i = 5, int32(42), 6 +} +_ = i +`, + want: map[Level]string{ + Red: ` +var i int +if m2.HasI32() { + i = 5 + m2.SetI32(int32(42)) + i = 6 +} +_ = i +`, + }, + }, + + { + desc: "multiple assignments, lhs proto field dereference", + srcfiles: []string{"pkg.go"}, + in: ` +var i int +if val := m2.I32; val != nil { + *val, i = int32(42), 5 + i, *val = 6, int32(43) +} +_ = i +`, + want: map[Level]string{ + Red: ` +var i int +if m2.HasI32() { + m2.SetI32(int32(42)) + i = 5 + i = 6 + m2.SetI32(int32(43)) +} +_ = i +`, + }, + }, + + { + desc: "multi-assign, lhs and rhs", + srcfiles: []string{"pkg.go"}, + in: ` +var i int +if val := m2.I32; val != nil { + *val, i = *val+2, 5 + i, *val = 6, *val+5 +} +_ = i +`, + want: map[Level]string{ + Red: ` +var i int +if m2.HasI32() { + m2.SetI32(m2.GetI32() + 2) + i = 5 + i = 6 + m2.SetI32(m2.GetI32() + 5) +} +_ = i +`, + }, + }, + + { + desc: "ifstmt: potential side effect initializer", + srcfiles: []string{"pkg.go"}, + extra: "func f(m *pb2.M2) *int32 { return m.I32 }", + in: ` +if val := f(m2); val != nil { + *val = int32(42) +} +`, + want: map[Level]string{ + Red: ` +if val := f(m2); val != nil { + *val = int32(42) +} +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestEnum(t *testing.T) { + tests := []test{ + { + desc: "basic case", + srcfiles: []string{"pkg.go"}, + in: ` +_ = &pb2.M2{ + E: pb2.M2_E_VAL.Enum(), +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetE(pb2.M2_E_VAL) +_ = m2h2 +`, + }, + }, + + { + desc: "free function with explicit receiver", + srcfiles: []string{"pkg.go"}, + in: ` +_ = &pb2.M2{ + E: pb2.M2_Enum.Enum(pb2.M2_E_VAL), +} +`, + want: map[Level]string{ + Red: ` +m2h2 := &pb2.M2{} +m2h2.SetE(pb2.M2_E_VAL) +_ = m2h2 +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestNameGeneration(t *testing.T) { + tests := []test{ + { + desc: "basic case", + srcfiles: []string{"pkg.go"}, + in: ` +m2h2 := 5 +{ + _ = &pb2.M2{ + S: proto.String("Hello World!"), + } +} +_ = m2h2 +`, + want: map[Level]string{ + Red: ` +m2h2 := 5 +{ + m2h3 := &pb2.M2{} + m2h3.SetS("Hello World!") + _ = m2h3 +} +_ = m2h2 +`, + }, + }, + + { + desc: "local definition", + srcfiles: []string{"pkg.go"}, + in: ` +if m2h2 := int32(5); m2h2 < 5 { + _ = &pb2.M2{ + S: proto.String("Hello World!"), + I32: proto.Int32(m2h2), + } +} +`, + want: map[Level]string{ + Red: ` +if m2h2 := int32(5); m2h2 < 5 { + m2h3 := &pb2.M2{} + m2h3.SetS("Hello World!") + m2h3.SetI32(m2h2) + _ = m2h3 +} +`, + }, + }, + + { + desc: "sub message", + srcfiles: []string{"pkg.go"}, + in: ` +if op2 := (&pb2.OtherProto2{}); op2 != nil { + _ = &pb2.OtherProto2{ + M: &pb2.OtherProto2{}, + Ms: []*pb2.OtherProto2{op2}, + } +} +`, + want: map[Level]string{ + Red: ` +if op2 := (&pb2.OtherProto2{}); op2 != nil { + op2h2 := &pb2.OtherProto2{} + op2h2.SetM(&pb2.OtherProto2{}) + op2h2.SetMs([]*pb2.OtherProto2{op2}) + _ = op2h2 +} +`, + }, + }, + + { + desc: "within if statement", + srcfiles: []string{"pkg.go"}, + extra: "func extra(*pb2.M2) bool { return true }", + in: ` +if extra(&pb2.M2{I32: proto.Int32(42)}) { +} + +if extra(&pb2.M2{I32: proto.Int32(42)}) { +} +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetI32(42) +if extra(m2h2) { +} + +m2h3 := &pb2.M2{} +m2h3.SetI32(42) +if extra(m2h3) { +} +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestParentheses(t *testing.T) { + tests := []test{ + { + desc: "basic case", + srcfiles: []string{"pkg.go"}, + in: ` +p := int32(42) +m2.I32 = &((p)) +`, + want: map[Level]string{ + Red: ` +p := int32(42) +m2.SetI32(p) +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestUnshadow(t *testing.T) { + tests := []test{ + { + desc: "struct literal", + srcfiles: []string{"pkg.go"}, + in: ` +m2 = &pb2.M2{M: m2} +`, + want: map[Level]string{ + Green: ` +m2h2 := m2 +m2 = &pb2.M2{} +m2.SetM(m2h2) +`, + }, + }, + + { + desc: "selectorexpr", + srcfiles: []string{"pkg.go"}, + in: ` +var unrelated struct{ + zone string +} +zone := &pb2.M2{S: proto.String(unrelated.zone)} +_ = zone +`, + want: map[Level]string{ + Green: ` +var unrelated struct { + zone string +} +zone := &pb2.M2{} +zone.SetS(unrelated.zone) +_ = zone +`, + }, + }, + + { + desc: "struct literal, define", + srcfiles: []string{"pkg.go"}, + in: ` +{ + m2 := &pb2.M2{M: m2} + _ = m2 +} +`, + want: map[Level]string{ + Green: ` +{ + m2h2 := m2 + m2 := &pb2.M2{} + m2.SetM(m2h2) + _ = m2 +} +`, + }, + }, + + { + desc: "append", + srcfiles: []string{"pkg.go"}, + in: ` +all := &pb2.M2{} +all.Ms = append(all.Ms, m2) +`, + want: map[Level]string{ + Green: ` +all := &pb2.M2{} +all.SetMs(append(all.GetMs(), m2)) +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestConflictingNames(t *testing.T) { + tests := []test{ + { + desc: "struct literal", + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.SetterNameConflict{ + Stat: proto.Int32(int32(5)), + SetStat: proto.Int32(int32(42)), + GetStat_: proto.Int32(int32(42)), + HasStat: proto.Int32(int32(42)), + ClearStat: proto.Int32(int32(42)), +} +_ = m +_ = *m.Stat +if m.Stat != nil || m.Stat == nil { + m.Stat = nil +} +`, + want: map[Level]string{ + // The SetGetStat_ is wrong. It should be SetGetStat. + // Fixing it requires the proto descriptor and thus, + // is not worth it. + // Technically we should also generate Get_Stat() + // instead of GetStat() but again it requires the + // proto descriptor. + // Both of these cases should be extremely rare. + Green: ` +m := &pb2.SetterNameConflict{} +m.Set_Stat(int32(5)) +m.SetSetStat(int32(42)) +m.SetGetStat_(int32(42)) +m.SetHasStat(int32(42)) +m.SetClearStat(int32(42)) +_ = m +_ = m.GetStat() +if m.Has_Stat() || !m.Has_Stat() { + m.Clear_Stat() +} +`, + }, + }, + } + + runTableTests(t, tests) +}
diff --git a/internal/fix/debug.go b/internal/fix/debug.go new file mode 100644 index 0000000..479e150 --- /dev/null +++ b/internal/fix/debug.go
@@ -0,0 +1,74 @@ +// 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 fix + +import ( + "bytes" + "fmt" + "go/types" + "reflect" + + "github.com/dave/dst" +) + +// dump exists for debugging purposes. It prints information about provided objects. Don't delete it. +func (c *cursor) dump(args ...any) { + toString := func(n dst.Node) string { + buf := new(bytes.Buffer) + err := dst.Fprint(buf, n, func(name string, val reflect.Value) bool { + return dst.NotNilFilter(name, val) && name != "Obj" && name != "Decs" + }) + if err != nil { + buf = bytes.NewBufferString("<can't print the expression>") + } + return buf.String() + } + defer fmt.Println("------------------------------") + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered", r) + } + }() + fmt.Println("==============================") + for _, a := range args { + switch a := a.(type) { + case nil: + fmt.Println("<nil>") + case dst.Expr: + fmt.Print("EXPR: `") + fmt.Printf("DST TYPE: %T\n", a) + fmt.Println(toString(a)) + fmt.Println("`") + fmt.Println("EXPR TYPE: ", c.typeOf(a)) + case *dst.FieldList, *dst.Field: + fmt.Printf("DST TYPE: %T\n", a) + fmt.Println("*dst.FieldList and *dst.Field can't be printed") + case dst.Node: + fmt.Printf("DST TYPE: %T\n", a) + if _, ok := a.(*dst.FieldList); ok { + fmt.Print("*dst.FieldList can't be printed") + } else { + fmt.Println(toString(a)) + } + fmt.Println("") + case string: + fmt.Println("label:", a) + case bool, int: + fmt.Println("value:", a) + case types.Type: + if a == nil { + fmt.Println("<no type information>") + } else { + fmt.Printf("%T\n", a) + } + case types.TypeAndValue: + fmt.Printf("TypeAndValue%+v\n", a) + case types.Object: + fmt.Printf("Object: .Package=%s .Name=%s .Id=%s\n", a.Pkg(), a.Name(), a.Id()) + default: + fmt.Printf("unrecognized argument of type %T\n", a) + } + } +}
diff --git a/internal/fix/diff.go b/internal/fix/diff.go new file mode 100644 index 0000000..054f11c --- /dev/null +++ b/internal/fix/diff.go
@@ -0,0 +1,70 @@ +// 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 fix + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + "syscall" +) + +func udiff(x, y []byte) ([]byte, error) { + if bytes.Equal(x, y) { + return nil, nil + } + xp, err := pipe(x) + if err != nil { + return nil, err + } + defer xp.Close() + yp, err := pipe(y) + if err != nil { + return nil, err + } + defer yp.Close() + + var stderr bytes.Buffer + cmd := exec.Command("diff", "-u", "/dev/fd/3", "/dev/fd/4") + cmd.ExtraFiles = []*os.File{xp, yp} + cmd.Stderr = &stderr + stdout, err := cmd.Output() + if ee, ok := err.(*exec.ExitError); ok { + if ws, ok := ee.Sys().(syscall.WaitStatus); ok && ws.ExitStatus() == 1 { + // exit status 1 means there's a diff, but no other failure + err = nil + } + } + if err != nil { + return nil, err + } + if stderr.Len() != 0 { + return nil, fmt.Errorf("diff: %s", &stderr) + } + nl := []byte("\n") + lines := bytes.Split(stdout, nl) + if len(lines) < 2 { + return stdout, nil + } + if strings.HasPrefix(string(lines[0]), "--- /dev/fd/3\t") && + strings.HasPrefix(string(lines[1]), "+++ /dev/fd/4\t") { + stdout = bytes.Join(lines[2:], nl) + } + return stdout, nil +} + +func pipe(data []byte) (*os.File, error) { + pr, pw, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("os.Pipe: %v", err) + } + go func() { + pw.Write(data) + pw.Close() + }() + return pr, nil +}
diff --git a/internal/fix/fix.go b/internal/fix/fix.go new file mode 100644 index 0000000..17256b5 --- /dev/null +++ b/internal/fix/fix.go
@@ -0,0 +1,519 @@ +// 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 fix rewrites Go packages to use opaque version of the Go protocol +// buffer API. +package fix + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" + "go/types" + "os" + "reflect" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/dave/dst/dstutil" + log "github.com/golang/glog" + "golang.org/x/tools/go/types/typeutil" + spb "google.golang.org/open2opaque/internal/dashboard" + "google.golang.org/open2opaque/internal/ignore" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/o2o/syncset" +) + +// Level represents the riskiness of a fix ranging from "safe to submit" to +// "needs fixes by humans". +type Level string + +const ( + // None means that no transforms to the code are applied. Useful for + // gathering statistics about unmodified code. + None = Level("none") + + // Green fixes are considered safe to submit without human review. Those + // fixes preserve the behavior of the program. + Green = Level("green") + + // Yellow fixes are safe to submit except for programs that depend on + // unspecified behavior, internal details, code that goes against the + // good coding style or guidelines, etc. + // Yellow fixes should be reviewed. + Yellow = Level("yellow") + + // Red fixes can change behavior of the program. Red fixes are proposed + // when we can't prove that the fix is safe or when the fix results in + // code that we don't consider readable. + // Red fixes should go through extensive review and analysis. + Red = Level("red") +) + +// ge returns true if lvl is greater or equal to rhs. +func (lvl Level) ge(rhs Level) bool { + switch lvl { + case None: + return rhs == None + case Green: + return rhs == None || rhs == Green + case Yellow: + return rhs == None || rhs == Green || rhs == Yellow + case Red: + return true + } + panic("unreachable; lvl = '" + lvl + "'") +} + +// le returns true if lvl is less or equal to rhs. +func (lvl Level) le(rhs Level) bool { + return lvl == rhs || !lvl.ge(rhs) +} + +// FixedFile represents a single file after applying fixes. +type FixedFile struct { + // Path to the file + Path string + OriginalCode string // Code before applying fixes. + Code string // Code after applying fixes. + Modified bool // Whether the file was modified by this tool. + Generated bool // Whether the file is a generated file. + Stats []*spb.Entry // List of proto accesses in Code (i.e. after applying rewrites). + Drifted bool // Whether the file has drifted between CBT and HEAD. + RedFixes map[unsafeReason]int // Number of fixes per unsafe category. +} + +func (f *FixedFile) String() string { + return fmt.Sprintf("[FixedFile %s Code=<redacted,len=%d> Modified=%t Generated=%t Stats=<redacted,len=%d>]", + f.Path, len(f.Code), f.Modified, f.Generated, len(f.Stats)) +} + +// Result describes what was fixed. For all change levels. +type Result map[Level][]*FixedFile + +// AllStats returns all of the generated stats entries. +func (r Result) AllStats() []*spb.Entry { + var stats []*spb.Entry + for _, lvl := range []Level{None, Green, Yellow, Red} { + for _, f := range r[lvl] { + if lvl > None && !f.Modified { + continue + } + stats = append(stats, f.Stats...) + } + } + return stats +} + +// ReportStats calls report on each given stats entry after setting +// its Status field based on err. If err is non-nil, it also reports +// an empty entry for pkgPath. +func ReportStats(stats []*spb.Entry, pkgPath string, err error, report func(*spb.Entry)) { + st := &spb.Status{Type: spb.Status_OK} + if err != nil { + st = &spb.Status{ + Type: spb.Status_FAIL, + Error: strings.TrimSpace(err.Error()), + } + report(&spb.Entry{ + Status: st, + Location: &spb.Location{ + Package: pkgPath, + }, + }) + } + + for _, e := range stats { + if e.Status == nil { + e.Status = st + } + report(e) + } +} + +// rewrite describes transformations done on a DST with a pre- and post- +// traversal. Exactly one of the pre/post must be set. +type rewrite struct { + name string + pre func(c *cursor) bool + post func(c *cursor) bool +} + +var rewrites []rewrite + +// typesInfo contains type information for a type-checked package similar to types.Info but with +// dst.Node instead of ast.Node. +type typesInfo struct { + types map[dst.Expr]types.TypeAndValue + uses map[*dst.Ident]types.Object + defs map[*dst.Ident]types.Object + astMap map[dst.Node]ast.Node + dstMap map[ast.Node]dst.Node +} + +// typeOf returns the types.Type of the given expression or nil if not found. It is equivalent to +// types.Info.TypeOf. +func (info *typesInfo) typeOf(e dst.Expr) types.Type { + if t, ok := info.types[e]; ok { + return t.Type + } + if id, _ := e.(*dst.Ident); id != nil { + if obj := info.objectOf(id); obj != nil { + return obj.Type() + } + } + return nil +} + +// objectOf returns the types.Object denoted by the given id, or nil if not found. It is equivalent +// to types.Info.TypeOf. +func (info *typesInfo) objectOf(id *dst.Ident) types.Object { + if obj := info.defs[id]; obj != nil { + return obj + } + return info.uses[id] +} + +// A cacheEntry stores a proto type (if any). Presence of a cacheEntry indicates +// that the type in question has been processed before, no matter whether +// protoType is nil or non-nil. +type cacheEntry struct { + protoType types.Type +} + +func init() { + t, err := types.Eval(token.NewFileSet(), nil, token.NoPos, "func(){}()") + if err != nil { + panic("can't initialize void type: " + err.Error()) + } + if !t.IsVoid() { + panic("can't initialize the void type") + } + voidType = t +} + +// BuilderUseType categorizes when builders instead of setters are used to +// rewrite struct literal initialization. +type BuilderUseType int + +const ( + // BuildersNowhere means never use builders + BuildersNowhere BuilderUseType = 0 + // BuildersEverywhere means always use builders + BuildersEverywhere BuilderUseType = 1 + // BuildersTestsOnly means use builders only in tests + BuildersTestsOnly BuilderUseType = 2 + // BuildersEverywhereExceptPromising means always use builders, except for + // .go files that touch promising protos (many fleet-wide unmarshals). + BuildersEverywhereExceptPromising BuilderUseType = 3 +) + +// ConfiguredPackage contains a package and all configuration necessary to +// rewrite the package. +type ConfiguredPackage struct { + Loader loader.Loader + Pkg *loader.Package + TypesToUpdate map[string]bool + BuilderTypes map[string]bool + BuilderLocations *ignore.List + Levels []Level + ProcessedFiles *syncset.Set + ShowWork bool + Testonly bool + UseBuilders BuilderUseType +} + +// Fix fixes a Go package. +func (cpkg *ConfiguredPackage) Fix() (Result, error) { + defer func() { + if r := recover(); r != nil { + panic(fmt.Sprintf("can't process package: %v", r)) + } + }() + + // Pairing of loader.File with associated dst.File. + type filePair struct { + loaderFile *loader.File + dstFile *dst.File + } + files := []filePair{} + + // Convert AST to DST and also produce corresponding typesInfo. + dec := decorator.NewDecorator(cpkg.Pkg.Fileset) + for _, f := range cpkg.Pkg.Files { + dstFile, err := dec.DecorateFile(f.AST) + if err != nil { + return nil, err + } + files = append(files, filePair{f, dstFile}) + } + info := dstTypesInfo(cpkg.Pkg.TypeInfo, dec) + + // Only check for file drift (between Compilations Bigtable and Piper HEAD) + // when working in a CitC client, not when running as FlumeGo job in prod. + driftCheck := false + if wd, err := os.Getwd(); err == nil { + driftCheck = strings.HasPrefix(wd, "/google/src/cloud/") + } + + out := make(Result) + for _, rec := range files { + f := rec.loaderFile + if f.LibraryUnderTest { + // Skip library code: non-test code uses setters instead of + // builders, so it is important to skip library code when processing + // the _test target. + log.Infof("skipping library file %s", f.Path) + continue + } + if !cpkg.ProcessedFiles.Add(f.Path) { + continue + } + + dstFile := rec.dstFile + fmtSource := func() string { + var buf bytes.Buffer + if err := decorator.Fprint(&buf, dstFile); err != nil { + log.Fatalf("BUG: decorator.Fprint: %v", err) + } + return buf.String() + } + c := &cursor{ + pkg: cpkg.Pkg, + curFile: f, + curFileDST: dstFile, + imports: newImports(cpkg.Pkg.TypePkg, f.AST), + typesInfo: info, + loader: cpkg.Loader, + lvl: None, + typesToUpdate: cpkg.TypesToUpdate, + builderTypes: cpkg.BuilderTypes, + builderLocations: cpkg.BuilderLocations, + shouldLogCompositeTypeCache: new(typeutil.Map), + shouldLogCompositeTypeCacheNoPtr: new(typeutil.Map), + debugLog: make(map[string][]string), + builderUseType: cpkg.UseBuilders, + testonly: cpkg.Testonly, + helperVariableNames: make(map[string]bool), + numUnsafeRewritesByReason: map[unsafeReason]int{}, + } + knownNoType := exprsWithNoType(c, dstFile) + out[None] = append(out[None], &FixedFile{ + Path: f.Path, + Code: f.Code, + Generated: f.Generated, + Stats: stats(c, dstFile, f.Generated), + }) + for _, lvl := range cpkg.Levels { + if lvl == None { + continue + } + if cpkg.ShowWork { + log.Infof("----- LEVEL %s -----", lvl) + } + c.imports.importsToAdd = nil + for _, r := range rewrites { + before := "" + if cpkg.ShowWork { + before = fmtSource() + } + + c.lvl = lvl + if (r.pre != nil) == (r.post != nil) { + // We enforce this so that it's easier to accurately detect + // which DST transformation loses type information. + panic(fmt.Sprintf("exactly one rewrite.pre or rewrite.post must be set; r.pre set: %t; r.post set: %t", r.pre != nil, r.post != nil)) + } + if r.pre != nil { + dstutil.Apply(dstFile, makeApplyFn(r.name, cpkg.ShowWork, r.pre, c), nil) + } + if r.post != nil { + dstutil.Apply(dstFile, nil, makeApplyFn(r.name, cpkg.ShowWork, r.post, c)) + } + // Walk the dst and verify that all expressions that should have + // the type set, have the type set. The idea is that we can run + // this over all our code and identify type bugs in + // open2opaque rewrites. + // + // + // We've considered the following alternative: + // + // for each transformation (e.g. green 'hasPre') + // repeat until there are no changes: + // type-check + // apply the transformation + // + // We've discarded this approach because it prevents doing + // transformation that can't be type checked. For example: + // introducing builders. The problem is that: + // - not all protos are on the open_struct API + // - we use an offline job to provide type information for + // dependencies and can't easily make it generate the new API + dstutil.Apply(dstFile, func(cur *dstutil.Cursor) bool { + x, ok := cur.Node().(dst.Expr) + if !ok { + return true + } + if knownNoType[x] { + return true + } + if _, ok := c.typesInfo.types[x]; !ok { + buf := new(bytes.Buffer) + err := dst.Fprint(buf, x, func(name string, v reflect.Value) bool { + return name != "Decs" && name != "Obj" && name != "Path" + }) + if err != nil { + buf = bytes.NewBufferString("<can't print the expression>") + } + panic(fmt.Sprintf("BUG: can't determine type of expression after a rewrite; level: %s; file: %s; rewrite %s; expr:\n%s", + c.lvl, rec.loaderFile.Path, r.name, buf)) + } + return true + }, nil) + + if cpkg.ShowWork { + after := fmtSource() + // We are intentionally using udiff instead of + // cmp.Diff here, because it is too cumbersome to get a + // line-based diff out of cmp.Diff. + // + // While udiff calling out to diff(1) is not the most + // efficient arrangement, at least the output format is + // familiar to readers. + diff, err := udiff([]byte(before), []byte(after)) + if err != nil { + return nil, err + } + if diff != nil { + log.Infof("rewrite %s changed:\n%s", r.name, string(diff)) + } + } + } + + if len(c.imports.importsToAdd) > 0 { + dstutil.Apply(dstFile, nil, func(cur *dstutil.Cursor) bool { + if _, ok := cur.Node().(*dst.ImportSpec); !ok { + return true // skip node, looking for ImportSpecs only + } + decl, ok := cur.Parent().(*dst.GenDecl) + if !ok { + panic(fmt.Sprintf("BUG: parent of ImportSpec is type %T, wanted GenDecl", cur.Parent())) + } + if cur.Index() < len(decl.Specs)-1 { + return true // skip import, waiting for the last one + } + // This is the last import, so we add to the very end of + // the import list. + for _, imp := range c.imports.importsToAdd { + cur.InsertAfter(imp) + c.setType(imp.Path, types.Typ[types.Invalid]) + } + return false // import added, abort traversal + }) + } + + var buf bytes.Buffer + if err := decorator.Fprint(&buf, dstFile); err != nil { + return nil, err + } + code := buf.String() + modified := f.Code != code + drifted := false + if modified && !f.Generated && driftCheck && + // The paths that our unit tests use (test/pkg/...) do not refer + // to actual files and hence cannot be read. + !strings.HasPrefix(f.Path, "test/pkg/") { + // Check whether the source has changed between the local CitC + // client and reading it from the go/compilations-bigtable + // loader. + b, err := os.ReadFile(f.Path) + if err != nil { + return nil, err + } + // Our loader formats the source when loading from + // go/compilations-bigtable, so we need to format here, too. + formattedContents, err := format.Source(b) + if err != nil { + return nil, err + } + drifted = f.Code != string(formattedContents) + } + out[lvl] = append(out[lvl], &FixedFile{ + Path: f.Path, + OriginalCode: f.Code, + Code: code, + Modified: modified, + Generated: f.Generated, + Drifted: drifted, + Stats: stats(c, dstFile, f.Generated), + RedFixes: c.numUnsafeRewritesByReason, + }) + } + } + return out, nil +} + +func exprsWithNoType(cur *cursor, f *dst.File) map[dst.Expr]bool { + out := map[dst.Expr]bool{} + dstutil.Apply(f, func(c *dstutil.Cursor) bool { + x, ok := c.Node().(dst.Expr) + if !ok { + return true + } + if _, ok := cur.typesInfo.types[x]; !ok { + out[x] = true + } + return true + }, nil) + return out +} + +// dstTypesInfo generates typesInfo from given types.Info and dst mapping. +func dstTypesInfo(orig *types.Info, dec *decorator.Decorator) *typesInfo { + dstMap := dec.Dst + info := &typesInfo{ + types: map[dst.Expr]types.TypeAndValue{}, + defs: map[*dst.Ident]types.Object{}, + uses: map[*dst.Ident]types.Object{}, + astMap: dec.Ast.Nodes, + dstMap: dec.Dst.Nodes, + } + + for astExpr, tav := range orig.Types { + if dstExpr, ok := dstMap.Nodes[astExpr]; ok { + info.types[dstExpr.(dst.Expr)] = tav + } + } + + for astIdent, obj := range orig.Defs { + if dstIdent, ok := dstMap.Nodes[astIdent]; ok { + info.defs[dstIdent.(*dst.Ident)] = obj + } + } + + for astIdent, obj := range orig.Uses { + if dstIdent, ok := dstMap.Nodes[astIdent]; ok { + info.uses[dstIdent.(*dst.Ident)] = obj + } + } + + return info +} + +// makeApplyFn adapts a rewrite to work with dstutil.Apply package. +func makeApplyFn(name string, showWork bool, f func(c *cursor) bool, cur *cursor) dstutil.ApplyFunc { + if f == nil { + return nil + } + cur.rewriteName = name + return func(c *dstutil.Cursor) bool { + cur.Logf("entering") + defer cur.Logf("leaving") + cur.Cursor = c + return f(cur) + } +}
diff --git a/internal/fix/fix_test.go b/internal/fix/fix_test.go new file mode 100644 index 0000000..3a63eed --- /dev/null +++ b/internal/fix/fix_test.go
@@ -0,0 +1,120 @@ +// 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 fix + +import ( + "context" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/open2opaque/internal/o2o/fakeloader" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/o2o/syncset" +) + +func TestLevelsComparison(t *testing.T) { + all := []Level{None, Green, Yellow, Red} + + var got []Level + for i := 0; i < 10; i++ { + got = append(got, all...) + } + sort.Slice(got, func(i, j int) bool { + return got[i].le(got[j]) + }) + + var want []Level + for _, lvl := range all { + for i := 0; i < 10; i++ { + want = append(want, lvl) + } + } + + if d := cmp.Diff(want, got); d != "" { + t.Fatalf("Sort returned %v want %v; diff:\n%s", got, want, d) + } +} + +func TestPopulatesModifiedAndGeneratedFlags(t *testing.T) { + ruleName := "fake" + prefix := ruleName + "/" + pathPrefix := prefix + + l := fakeloader.NewFakeLoader( + map[string][]string{ruleName: []string{ + prefix + "updated.go", + prefix + "nochange.go", + prefix + "generated_updated.go", + prefix + "generated_nochange.go", + }}, + map[string]string{ + prefix + "updated.go": `package test + +type MessageState struct{} + +type M struct { + state MessageState ` + "`" + `protogen:"hybrid.v1"` + "`" + ` + S *string +} + +func (*M) Reset() { } +func (*M) String() string { return "" } +func (*M) ProtoMessage() { } + +func f(m *M) { + _ = m.S != nil +} +`, + prefix + "nochange.go": "package test\n", + }, + map[string]string{ + prefix + "generated_updated.go": `package test + +func g(m *M) { + _ = m.S != nil +} +`, + prefix + "generated_nochange.go": "package test\n", + }, + nil) + + ctx := context.Background() + for _, lvl := range []Level{None, Green, Yellow, Red} { + t.Run(string(lvl), func(t *testing.T) { + pkg, err := loader.LoadOne(ctx, l, &loader.Target{ID: ruleName}) + if err != nil { + t.Fatalf("Can't load %q: %v:", ruleName, err) + } + + cPkg := ConfiguredPackage{ + Pkg: pkg, + Levels: []Level{lvl}, + ProcessedFiles: syncset.New(), + UseBuilders: BuildersTestsOnly, + } + got, err := cPkg.Fix() + if err != nil { + t.Fatalf("Can't fix %q: %v", ruleName, err) + } + + want := []*FixedFile{ + {Path: pathPrefix + "updated.go", Modified: true, Generated: false}, + {Path: pathPrefix + "nochange.go", Modified: false, Generated: false}, + {Path: pathPrefix + "generated_updated.go", Modified: true, Generated: true}, + {Path: pathPrefix + "generated_nochange.go", Modified: false, Generated: true}, + } + ignored := []string{"Code", "Stats", "OriginalCode", "RedFixes"} + if lvl == None { + ignored = append(ignored, "Modified") + } + + if d := cmp.Diff(want, got[lvl], cmpopts.IgnoreFields(FixedFile{}, ignored...)); d != "" { + t.Fatalf("Package() = %s, want %s; diff:\n%s", got[lvl], want, d) + } + }) + } +}
diff --git a/internal/fix/fixcursor.go b/internal/fix/fixcursor.go new file mode 100644 index 0000000..0269d76 --- /dev/null +++ b/internal/fix/fixcursor.go
@@ -0,0 +1,859 @@ +// 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 fix + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + "strings" + "unicode" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" + log "github.com/golang/glog" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/typeutil" + "google.golang.org/open2opaque/internal/ignore" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/protodetecttypes" +) + +// cursor is an argument to rewrite transformations. Rewrites can modify this state to share +// information with other DST nodes in the same package. +type cursor struct { + *dstutil.Cursor // Describes node to rewrite (referred as "current node" below). See dstutil.Cursor. + + pkg *loader.Package // Package containing the current node. + curFile *loader.File + curFileDST *dst.File + imports *imports // Imports defined in the file containing the current node. + typesInfo *typesInfo + loader loader.Loader + + lvl Level // Specifies level of rewrites to apply to the current node. + + // A set of types to consider when updating code. Empty set means "update all". + typesToUpdate map[string]bool + + // A set of types for which to always use builders, not setters. + // (e.g. "google.golang.org/protobuf/types/known/timestamppb"). + // + // An empty (or nil) builderTypes means: use setters or builders for + // production/test code respectively (or follow the -use_builders flag if + // set). + builderTypes map[string]bool + + // A list of files for which to always use builders, not setters. + builderLocations *ignore.List + + // A cache of shouldLogCompositeType results. The value for a given key can be: + // - missing: no information for that type + // - nil: either: + // - the type is currently being processed (e.g. struct fields can refer + // to the struct type), or + // - the type was processed and it does not depend on protos + // + // - non-nil: cached result for a type that should be processed + // The cache serves as both: an optimization and a way to address cycles (see TestStats/recursive_type). + // + // The NoPtr version is used for calls with followPointers=false + shouldLogCompositeTypeCache *typeutil.Map + shouldLogCompositeTypeCacheNoPtr *typeutil.Map + + // rewriteName is the function name of the open2opaque rewrite step that is + // currently running, e.g. appendProtosPre. + rewriteName string + + // ASTID is the astv.ASTID() for the current DST node. + ASTID string + + // debugLog will be passed as astv.File.DebugLog + debugLog map[string][]string + + // Where should the tool use builders? + builderUseType BuilderUseType + + testonly bool // does this file belong to a testonly library? + + helperVariableNames map[string]bool + + numUnsafeRewritesByReason map[unsafeReason]int +} + +func (c *cursor) Logf(format string, a ...any) { + if c.ASTID == "" { + return + } + msg := fmt.Sprintf(string(c.lvl)+"/"+c.rewriteName+": "+format, a...) + c.debugLog[c.ASTID] = append(c.debugLog[c.ASTID], msg) +} + +func (c *cursor) Replace(n dst.Node) { + c.Cursor.Replace(n) +} + +type unsafeReason int + +const ( + // Unknown means the reason not nown or ambiguous + Unknown unsafeReason = iota + // PointerAlias means the rewrite removes pointer aliasing + PointerAlias + // SliceAlias means the rewrite removes slice aliasing + SliceAlias + // InexpressibleAPIUsage means the rewrite changes the behavior because + // the original behavior cannot be expressed in the opaque API (this + // usually leads to build failures). + InexpressibleAPIUsage + // PotentialBuildBreakage means the rewrite might induce a build breakage. + PotentialBuildBreakage + // EvalOrderChange means the evaluation order changes by the rewrite + // (e.g. multi-assignments are rewritten into multiple single + // assignments). + EvalOrderChange + // IncompleteRewrite means the rewrite is incomplete and further manual + // changes are needed. In most cases a comment is left in the code. + IncompleteRewrite + // OneofFieldAccess means a oneof field is directly accessed in the + // hybrid/open API. This cannot be expressed in the opaque API. + OneofFieldAccess + // ShallowCopy means the rewrite contains a shallow copy (before and + // after) but shallow copies are unsafe in the opaque API. + ShallowCopy + // MaybeOneofChange means the rewrite produces code that might unset an + // oneof field that was previously set to an invalid state (type set + // but value not set). + MaybeOneofChange + // MaybeSemanticChange means the rewrite might produce invalid code + // because identifier refer to different objects than before. + MaybeSemanticChange + // MaybeNilPointerDeref means the rewrite might produce code that leads + // to nil pointer references that were not in the code before. + MaybeNilPointerDeref +) + +func (c *cursor) ReplaceUnsafe(n dst.Node, rt unsafeReason) { + c.numUnsafeRewritesByReason[rt]++ + c.Replace(n) +} + +func (c *cursor) InsertBefore(n dst.Node) { + c.Cursor.InsertBefore(n) +} + +func (c *cursor) underlyingTypeOf(expr dst.Expr) types.Type { + return c.typeOf(expr).Underlying() +} + +func (c *cursor) underlyingTypeOfOrNil(expr dst.Expr) types.Type { + t := c.typeOfOrNil(expr) + if t == nil { + return nil + } + return t.Underlying() +} + +func (c *cursor) isBuiltin(expr dst.Expr) bool { + tv, ok := c.typesInfo.types[expr] + return ok && tv.IsBuiltin() +} + +func (c *cursor) hasType(expr dst.Expr) bool { + return c.typesInfo.typeOf(expr) != nil +} + +func (c *cursor) typeAndValueOf(expr dst.Expr) (types.TypeAndValue, bool) { + tv, ok := c.typesInfo.types[expr] + return tv, ok +} + +func (c *cursor) typeOfOrNil(expr dst.Expr) types.Type { + t := c.typesInfo.typeOf(expr) + if t != nil { + return t + } + // We don't know the type of "_" (it doesn't have one). Don't panic + // because this is a known possibility and using "invalid" type here + // makes writing rules using c.typeOf easier. + if ident, ok := expr.(*dst.Ident); ok && ident.Name == "_" { + return types.Typ[types.Invalid] + } + return nil +} + +func (c *cursor) typeOf(expr dst.Expr) types.Type { + if t := c.typeOfOrNil(expr); t != nil { + return t + } + // The following code is unreachable. It exists to provide better error messages when there are + // bugs in how we handle type information (e.g. forget to update type info map). This is + // technically dead code but please don't delete it. It is very helpful during development. + buf := new(bytes.Buffer) + if err := dst.Fprint(buf, expr, dst.NotNilFilter); err != nil { + buf = bytes.NewBufferString("<can't print the expression>") + } + panic(fmt.Sprintf("don't know type for expression %T %s", expr, buf.String())) +} + +func (c *cursor) objectOf(ident *dst.Ident) types.Object { + return c.typesInfo.objectOf(ident) +} + +func (c *cursor) setType(expr dst.Expr, t types.Type) { + c.typesInfo.types[expr] = types.TypeAndValue{Type: t} +} + +func (c *cursor) setUse(ident *dst.Ident, o types.Object) { + c.typesInfo.uses[ident] = o + c.setType(ident, o.Type()) +} + +func (c *cursor) setVoidType(expr dst.Expr) { + c.typesInfo.types[expr] = voidType +} + +var voidType types.TypeAndValue + +func (c *cursor) isTest() bool { + return strings.HasSuffix(c.curFile.Path, "_test.go") || c.testonly +} + +func findEnclosingLiteral(file *dst.File, needle *dst.CompositeLit) *dst.CompositeLit { + var enclosing *dst.CompositeLit + dstutil.Apply(file, + func(cur *dstutil.Cursor) bool { + if enclosing == nil { + if cl, ok := cur.Node().(*dst.CompositeLit); ok { + enclosing = cl + } + } + + return true + }, + func(cur *dstutil.Cursor) bool { + if cur.Node() == needle { + return false // found the needle, stop traversal + } + + if cur.Node() == enclosing { + // Leaving the top-level *dst.CompositeLit, the needle was not + // found. + enclosing = nil + return true + } + + return true + }) + return enclosing +} + +func deepestNesting(lit *dst.CompositeLit) int { + var nesting, deepest int + dstutil.Apply(lit, + func(cur *dstutil.Cursor) bool { + if _, ok := cur.Node().(*dst.CompositeLit); ok { + nesting++ + if nesting > deepest { + deepest = nesting + } + } + return true + }, + func(cur *dstutil.Cursor) bool { + if _, ok := cur.Node().(*dst.CompositeLit); ok { + nesting-- + } + return true + }) + return deepest +} + +func (c *cursor) messagesInvolved(lit *dst.CompositeLit) int { + var involved int + dstutil.Apply(lit, + func(cur *dstutil.Cursor) bool { + if cur.Node() == lit { + // Do not count the top-level composite literal itself. + return true + } + if cl, ok := cur.Node().(*dst.CompositeLit); ok { + // Verify this composite literal is a proto message (builder or + // actual message), as opposed to a []int32{…}, for example. + t := c.typeOf(cl) + if strings.HasSuffix(t.String(), "_builder") { + involved++ + return true + } + if _, ok := c.messageTypeName(t); ok { + involved++ + } + } + return true + }, + nil) + return involved +} + +func (c *cursor) useBuilder(lit *dst.CompositeLit) bool { + if c.builderUseType == BuildersEverywhere { + return true + } + + if c.builderLocations.Contains(c.curFile.Path) { + return true + } + + // We treat codelabs like test code: performance cannot be a concern, so + // prefer readability. + isTestOrCodelab := c.isTest() || strings.Contains(c.curFile.Path, "codelab") + if c.builderUseType == BuildersTestsOnly && isTestOrCodelab { + return true + } + + elem := c.typeOf(lit) + if ptr, ok := elem.(*types.Pointer); ok { + elem = ptr.Elem() + } + if named, ok := elem.(*types.Named); ok { + obj := named.Obj() + typeName := obj.Pkg().Path() + "." + obj.Name() + c.Logf("always use builders for %q? %v", typeName, c.builderTypes[typeName]) + if c.builderTypes[typeName] { + return true + } + } + + // Check for the nesting level: too deeply nested literals cannot be + // converted to setters without a loss of readability, so stick to builders + // for these. + enclosing := findEnclosingLiteral(c.curFileDST, lit) + if enclosing == nil { + c.Logf("BUG?! No enclosing literal found for %p / %v", lit, lit) + return false // use setters + } + messagesInvolved := c.messagesInvolved(enclosing) + deepestNesting := deepestNesting(enclosing) + + c.Logf("CompositeLit nesting: %d", deepestNesting) + c.Logf("Messages involved: %d", messagesInvolved) + // NOTE(stapelberg): The deepestNesting condition might seem irrelevant + // thanks to the messagesInvolved condition, but keeping both allows us to + // adjust the number of either threshold without having to disable/re-enable + // the relevant code. + if deepestNesting >= 4 || messagesInvolved >= 4 { + return true // use builders + } + + return false // use setters +} + +// grabNameInScope finds and returns a free name (starting with prefix) in the +// provided scope, reserving it in the scope to ensure subsequent calls return a +// different name. +func grabNameInScope(pkg *types.Package, s *types.Scope, prefix string) string { + // Find a free name + name := prefix + cnt := 2 + for _, obj := s.LookupParent(name, token.NoPos); obj != nil; _, obj = s.LookupParent(name, token.NoPos) { + middle := "" + if unicode.IsNumber(rune(prefix[len(prefix)-1])) { + // Inject an extra h (stands for helper) if the prefix ends in a + // number: Generate m2h2 instead of m22, which might be misleading. + middle = "h" + } + name = fmt.Sprintf("%s%s%d", prefix, middle, cnt) + cnt++ + } + + // Insert an object with this name in the scope so that subsequent calls of + // grabNameInScope() cannot return the same name. + s.Insert(types.NewTypeName(token.NoPos, pkg, name, nil)) + + return name +} + +func (c *cursor) helperNameFor(n dst.Node, t types.Type) string { + // dst.Node objects do not have position information, so we need to + // look up the corresponding ast.Node to get to the scope. + astNode, ok := c.typesInfo.astMap[n] + if !ok { + log.Fatalf("BUG: %s: no corresponding go/ast node for dave/dst node %T / %p / %v", c.pkg.TypePkg.Path(), n, n, n) + } + inner := c.pkg.TypePkg.Scope().Innermost(astNode.Pos()) + if _, ok := astNode.(*ast.IfStmt); ok { + // An *ast.IfStmt creates a new scope. For inserting a helper before the + // *ast.IfStmt, we are interested in the parent scope, i.e. the scope + // that contains the *ast.IfStmt, and our helper variable. + inner = inner.Parent() + } + helperName := grabNameInScope(c.pkg.TypePkg, inner, helperVarNameForType(t)) + c.helperVariableNames[helperName] = true + return helperName +} + +func isCompositeLit(n, parent dst.Node) (*dst.CompositeLit, bool) { + // CompositeLits are often wrapped in a UnaryExpr (&pb.M2{…}), but not + // always: when defining a slice, the type is inferred, e.g. []*pb.M2{{…}}. + expr, ok := n.(dst.Expr) + if !ok { + return nil, false + } + if ue, ok := expr.(*dst.UnaryExpr); ok { + if ue.Op != token.AND { + return nil, false + } + expr = ue.X + } else { + // Ensure the parent is not a UnaryExpr to avoid triggering twice for + // UnaryExprs (once on the UnaryExpr, once on the contained + // CompositeLit). + if _, ok := parent.(*dst.UnaryExpr); ok { + return nil, false + } + } + lit, ok := expr.(*dst.CompositeLit) + return lit, ok +} + +// builderCLit returns a composite literal that is a non-empty one representing +// a protocol buffer. +func (c *cursor) builderCLit(n dst.Node, parent dst.Node) (*dst.CompositeLit, bool) { + lit, ok := isCompositeLit(n, parent) + if !ok { + return nil, false + } + if len(lit.Elts) == 0 { // Don't use builders for constructing zero values. + return nil, false + } + if !c.shouldUpdateType(c.typeOf(lit)) { + return nil, false + } + for _, e := range lit.Elts { + // This shouldn't be possible because of noUnkeyedLiteral (or + // XXX_NoUnkeyedLiterals) field included in all structs representing + // protocol buffers. We handle this just in case we run into a very old + // proto. + if _, ok := e.(*dst.KeyValueExpr); !ok { + return nil, false + } + } + return lit, true +} + +func (c *cursor) selectorForProtoMessageType(t types.Type) *dst.SelectorExpr { + // Get to the elementary type (pb.M2) if this is a pointer type (*pb.M2). + elem := t + if ptr, ok := elem.(*types.Pointer); ok { + elem = ptr.Elem() + } + named, ok := elem.(*types.Named) + if !ok { + log.Fatalf("BUG: proto message unexpectedly not a named type (but %T)?!", elem) + } + obj := named.Obj() + + sel := &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(obj.Pkg().Path())}, + Sel: &dst.Ident{Name: obj.Name()}, + } + c.setType(sel, t) + c.setType(sel.X, types.Typ[types.Invalid]) + c.setType(sel.Sel, types.Typ[types.Invalid]) + return sel +} + +// isSideEffectFree returns true if x can be safely called a different number of +// times after the rewrite. It returns false if it can't say for sure that +// that's the case. +// +// x must be one of "X", "X.F" or "X.GetF()" where any call is known to have no +// relevant side effects (e.g. method calls on protocol buffers). +func (c *cursor) isSideEffectFree(x dst.Expr) bool { + var sel *dst.SelectorExpr + switch x := x.(type) { + case *dst.Ident: + return true + case *dst.BasicLit: + return true + case *dst.IndexExpr: + return c.isSideEffectFree(x.Index) && c.isSideEffectFree(x.X) + case *dst.SelectorExpr: + sel = x + case *dst.CallExpr: + callSel, ok := x.Fun.(*dst.SelectorExpr) + if !ok { + return false + } + sel = callSel + if _, ok := c.messageTypeName(c.typeOf(callSel.X)); !ok { + return false + } + default: + return false + } + + var hasCall bool + dstutil.Apply(sel, func(cur *dstutil.Cursor) bool { + call, ok := cur.Node().(*dst.CallExpr) + if !ok { + return true + } + // If this is a method call on a proto then we don't report it as we know + // that those don't have relevant side effects. + if sel, ok := call.Fun.(*dst.SelectorExpr); ok { + if _, ok := c.messageTypeName(c.typeOf(sel.X)); ok { + return true + } + } + hasCall = true + return true + }, nil) + return !hasCall +} + +func isInterfaceVararg(t types.Type) bool { + vararg, ok := t.(*types.Slice) + if !ok { + return false + } + _, ok = vararg.Elem().(*types.Interface) + return ok +} + +func isString(t types.Type) bool { + b, ok := t.(*types.Basic) + return ok && b.Kind() == types.String +} + +// looksLikePrintf returns true for any function that looks like a printing one. For example: +// fmt.Print, log.Print, log.Info, (*testing.T).Error, etc. +// +// We can't enumerate all such functions so we use a heuristic that tries to classify a call +// expression based on its type. +func (c *cursor) looksLikePrintf(n dst.Node) bool { + // We say that a function/method is a printer if: + // - its first argument is a "string" (format) and it is followed by "...interface{}" argument (arguments), or + // - its sole argument is "...interface{}" (arguments) + call, ok := n.(*dst.CallExpr) + if !ok { + return false + } + if sel, ok := call.Fun.(*dst.SelectorExpr); ok { + if strings.HasSuffix(sel.Sel.Name, "Errorf") { + return true + } + } + sig, ok := c.typeOf(call.Fun).(*types.Signature) + if !ok { + return false + } + switch p := sig.Params(); p.Len() { + case 1: + return isInterfaceVararg(p.At(0).Type()) + case 2: + return isString(p.At(0).Type()) && isInterfaceVararg(p.At(1).Type()) + default: + return false + } +} + +// shouldUpdateType returns true for types which we consider to represent protocol buffers generated +// by the proto generator that the user requested to migrate. That is, types that should be +// considered for a rewrite during the open2opaque protocol buffer migration. +// +// There's also a shouldTrackType function, which returns true for types that we +// want to track for the migration purposes. For example, we want to track +// operations on type T in +// +// type T pb.M +// +// but we don't want to rewrite accesses to type yet. +func (c *cursor) shouldUpdateType(t types.Type) bool { + if !c.shouldTrackType(t) { + return false + } + _, ok := c.messageTypeName(t) + return ok +} + +// messageTypeName returns the name of the protocol buffer message with type +// t. It returns an empty string and false if t is not a protocol buffer message +// type. +func (c *cursor) messageTypeName(t types.Type) (name string, ok bool) { + name = t.String() + + if nt, ok := t.(*types.Named); ok && isPtr(nt.Underlying()) { + // A non-pointer named type whose underlying type is a pointer can be + // neither proto struct nor pointer to a proto struct. If we were to + // return "true" for such type, it was most likely "type T *pb.M" for some + // proto type "pb.M". + return "", false + } + + if p, ok := t.(*types.Pointer); ok { + t = p.Elem() + } + + nt, ok := t.(*types.Named) + if !ok { + return "", false + } + + // ProtoMessage exists on proto structs, but not on custom named types based + // on protos ("type T pb.M"). We don't want to rewrite code using such types + // with rules that use messageTypeName. + var hasProtoMessage bool + for i := 0; i < nt.NumMethods(); i++ { + if nt.Method(i).Name() == "ProtoMessage" { + hasProtoMessage = true + } + } + if !hasProtoMessage { + return "", false + } + + if st := (protodetecttypes.Type{T: t}); !st.IsMessage() || st.MessageAPI() == protodetecttypes.OpenAPI { + return "", false + } + + return strings.TrimPrefix(name, "*"), true +} + +// canAddr returns true the expression if it's legal to take address of that +// expression. +func (c *cursor) canAddr(x dst.Expr) bool { + tv, ok := c.typeAndValueOf(x) + if !ok { + return false + } + return tv.Addressable() +} + +// enclosingStmt returns the closest ast.Node that opens a scope (opener) and +// the ast.Node representing this scope (scope). E.g. for +// +// if ... { +// n +// } +// +// it returns the ast.IfStmt and the ast.IfStmt.Body nodes. +// Either both or none of the return values are nil. +func (c *cursor) enclosingASTStmt(n dst.Node) (scope ast.Node, opener ast.Node) { + lhsAst, ok := c.typesInfo.astMap[n] + if !ok { + c.Logf("BUG: no corresponding go/ast node for dave/dst node %T / %+v (was c.typesInfo.astMap not updated across rewrites?)", n, n) + return nil, nil + } + path, _ := astutil.PathEnclosingInterval(c.curFile.AST, lhsAst.Pos(), lhsAst.End()) + for i := 0; i < len(path)-1; i++ { + // Check for types that open a new scope. + switch path[i].(type) { + case *ast.BlockStmt, + *ast.CaseClause, + *ast.CommClause: + return path[i], path[i+1] + } + } + return nil, nil +} + +// useClearOrHas returns true for field accesses (sel) that should be handled +// with either Has or Clear call in the context of a comparison/assignment +// with/to nil. +func (c *cursor) useClearOrHas(sel *dst.SelectorExpr) bool { + if isOneof(c.typeOf(sel.Sel)) { + return true + } + f := c.objectOf(sel.Sel).(*types.Var) + // Handle messages (either proto2 or proto3) and proto2 enums. + if p, ok := f.Type().(*types.Pointer); ok { + if _, ok := p.Elem().(*types.Named); ok { + return true + } + } + // Handle non-enum proto2 scalars. + isProto2 := !isProto3Field(c.typeOf(sel.X), f.Name()) + if isProto2 { + switch ft := f.Type().(type) { + case *types.Pointer: + if _, ok := ft.Elem().(*types.Basic); ok { + return true + } + case *types.Slice: + // Use Clear for bytes field, but not other repeated fields. + if basic, ok := ft.Elem().(*types.Basic); ok && basic.Kind() == types.Uint8 { + return true + } + } + } + return false +} + +// isProto3Field returns true if given message is a proto3 message based on given field, else false. +// It uses the "protobuf" struct tag on the field to determine if it contains "proto3" or not. +// +// This function considers both value and pointer types to be protocol buffer messages. +func isProto3Field(m types.Type, field string) bool { + p, ok := m.Underlying().(*types.Pointer) + if ok { + m = p.Elem() + } + s, ok := m.Underlying().(*types.Struct) + if !ok { + return false + } + + numFields := s.NumFields() + for i := 0; i < numFields; i++ { + if s.Field(i).Name() == field { + // Following relies on current generator behavior of having def value always being at + // the end of the tag if it exists because value of "def" may contain ",". It also + // relies on the proto3 text not being in the first position. + st := s.Tag(i) + pb := reflect.StructTag(st).Get("protobuf") + if i := strings.Index(pb, ",def="); i > -1 { + pb = pb[:i] + } + return strings.Contains(pb, ",proto3") + } + } + return false +} + +// Exactly one of expr or t should be non-nil. expr can be nil only if t is *types.Basic +func (c *cursor) newProtoHelperCall(expr dst.Expr, t types.Type) dst.Expr { + if _, ok := t.(*types.Basic); expr == nil && !ok { + panic(fmt.Sprintf("t must be *types.Basic if expr is nil, but it is %T", t)) + } + if t == nil && expr == nil { + panic("t and expr can't be both nil") + } + if t == nil { + t = c.typeOf(expr) + } + + // Enums are represented as named types in generated files. + if t, ok := t.(*types.Named); ok { + fun := &dst.SelectorExpr{ + X: expr, + Sel: &dst.Ident{Name: "Enum"}, + } + c.setType(fun, types.NewSignature( + types.NewParam(token.NoPos, nil, "_", t), + types.NewTuple(), + types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.NewPointer(t))), + false)) + c.setType(fun.Sel, c.typeOf(fun)) + + out := &dst.CallExpr{Fun: fun} + c.setType(out, types.NewPointer(t)) + return out + } + bt := t.(*types.Basic) + if expr == nil { + expr = scalarTypeZeroExpr(c, bt) + } + hname := basicTypeHelperName(bt) + helper := c.imports.lookup(protoImport, hname) + out := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(protoImport)}, + Sel: &dst.Ident{Name: hname}, + }, + Args: []dst.Expr{expr}, + } + c.setType(out, types.NewPointer(t)) + if helper != nil { + c.setType(out.Fun, helper.Type()) + c.setUse(out.Fun.(*dst.SelectorExpr).Sel, helper) + } else { + // The "proto" package was not imported, so we do not have an actual + // type to assign. + c.setType(out.Fun, types.Typ[types.Invalid]) + c.setType(out.Fun.(*dst.SelectorExpr).Sel, types.Typ[types.Invalid]) + } + // We set the type for "proto" identifier to Invalid because that's consistent with what the + // typechecker does on new code. We need to distinguish "invalid" type from "no type was + // set" as the code panics on the later in order to catch issues with missing type updates. + c.setType(out.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid]) + + return out +} + +// trackedProtoFieldSelector is a wrapper around protoFieldSelector that only +// returns the field selector if the underlying proto message type should be +// updated (i.e. it was specified in -types_to_update). +func (c *cursor) trackedProtoFieldSelector(expr dst.Node) (*dst.SelectorExpr, bool) { + sel, ok := c.protoFieldSelector(expr) + if !ok { + return nil, false + } + t := c.typeOfOrNil(sel.X) + if t == nil { + return nil, false // skip over expression without type info (silo'ed?) + } + if !c.shouldUpdateType(t) { + return nil, false + } + return sel, true +} + +// protoFieldSelector checks whether expr is of the form "m.F" where m is a +// protocol buffer message and "F" is a field. It returns expr as DST selector +// if that's the case and true. Returns false otherwise. +func (c *cursor) protoFieldSelector(expr dst.Node) (*dst.SelectorExpr, bool) { + sel, ok := expr.(*dst.SelectorExpr) + if !ok { + return nil, false + } + t := c.typeOfOrNil(sel.X) + if t == nil { + return nil, false // skip over expression without type info (silo'ed?) + } + if _, messageType := c.messageTypeName(t); !messageType { + return nil, false + } + if strings.HasPrefix(sel.Sel.Name, "XXX_") { + return nil, false + } + if _, ok := c.underlyingTypeOf(sel.Sel).(*types.Signature); ok { + return nil, false + } + return sel, true +} + +// protoFieldSelectorOrAccessor is like protoFieldSelector, but also permits +// accessor methods like GetX, HasX, ClearX, SetX. If the expression is an +// accessor, the second return value contains its signature (nil otherwise). +func (c *cursor) protoFieldSelectorOrAccessor(expr dst.Node) (*dst.SelectorExpr, *types.Signature, bool) { + sel, ok := expr.(*dst.SelectorExpr) + if !ok { + return nil, nil, false + } + t := c.typeOfOrNil(sel.X) + if t == nil { + return nil, nil, false // skip over expression without type info (silo'ed?) + } + if _, messageType := c.messageTypeName(t); !messageType { + return nil, nil, false + } + if strings.HasPrefix(sel.Sel.Name, "XXX_") { + return nil, nil, false + } + if sig, ok := c.underlyingTypeOf(sel.Sel).(*types.Signature); ok { + if strings.HasPrefix(sel.Sel.Name, "Has") || + strings.HasPrefix(sel.Sel.Name, "Clear") || + strings.HasPrefix(sel.Sel.Name, "Set") || + strings.HasPrefix(sel.Sel.Name, "Get") { + return sel, sig, true + } + return nil, nil, false + } + return sel, nil, true +}
diff --git a/internal/fix/fiximports.go b/internal/fix/fiximports.go new file mode 100644 index 0000000..9131c32 --- /dev/null +++ b/internal/fix/fiximports.go
@@ -0,0 +1,158 @@ +// 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 fix + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "path/filepath" + "strconv" + "strings" + + "github.com/dave/dst" + log "github.com/golang/glog" + "golang.org/x/tools/go/ast/astutil" +) + +// imports provides an API to work with package imports. It is a convenience wrapper around +// type and AST information. +type imports struct { + path2pkg map[string]*types.Package + renameByPath map[string]string // import path -> name; for renamed packages + renameByName map[string]string // name -> import path + importsToAdd []*dst.ImportSpec +} + +// newImports creates imports for the package. +func newImports(pkg *types.Package, f *ast.File) *imports { + out := &imports{ + path2pkg: make(map[string]*types.Package), + renameByPath: make(map[string]string), + renameByName: make(map[string]string), + } + + // path2pkg maps from import path to *types.Package, but for the entire + // package-under-analysis, not just for the file-under-analysis. + path2pkg := make(map[string]*types.Package) + for _, imp := range pkg.Imports() { + path2pkg[imp.Path()] = imp + } + + astutil.Apply(f, func(c *astutil.Cursor) bool { + s, ok := c.Node().(*ast.ImportSpec) + if !ok { + return true + } + path, err := strconv.Unquote(s.Path.Value) + if err != nil { + log.Errorf("malformed source: %v", err) + return false + } + + if pkg, ok := path2pkg[path]; ok { + out.path2pkg[path] = pkg + } + + if s.Name == nil { // no rename + return false + } + out.renameByPath[path] = s.Name.Name + out.renameByName[s.Name.Name] = path + return false + }, nil) + + return out +} + +// name returns the name of import with the given import path. For example: +// +// "google.golang.org/protobuf/proto" => "proto" +// goproto "google.golang.org/protobuf/proto" => "goproto" +// +// In case the import does not yet exist, it will be queued for addition. +func (imp *imports) name(path string) string { + // Is the package already imported by the input source code? + if v, ok := imp.renameByPath[path]; ok { + return v + } + + // Check if we already tried to add the import. + for _, i := range imp.importsToAdd { + if s, err := strconv.Unquote(i.Path.Value); err == nil && s == path { + if i.Name != nil { + return i.Name.Name + } + return filepath.Base(path) + } + } + + // Import doesn't exist and we didn't try to add it yet. Add a new import. + p := imp.path2pkg[path] + if p == nil { + // path is an import path that does not occur in the source file. There + // are two situations in which this can happen: + // + // 1. The proto package (from third_party/golang/protobuf) was not + // imported, but is now necessary because helper functions like + // proto.String() are used after the rewrite. + // + // 2. A proto message is referenced without a corresponding import. For + // example, mypb.GetSubmessage() could be defined in the separate + // package myextrapb. + // + // We find an available name and add the required import(s). + name := imp.findAvailableName(path) + spec := &dst.ImportSpec{ + Path: &dst.BasicLit{Kind: token.STRING, Value: strconv.Quote(path)}, + } + if strings.HasSuffix(name, "pb") { + // The third_party proto package is not renamed, but all generated + // proto packages are. + spec.Name = &dst.Ident{Name: name} + } + imp.importsToAdd = append(imp.importsToAdd, spec) + return name + + } + return p.Name() +} + +// lookup returns a objects with givne name from import identified by the provided import path or nil if it doesn't exist. +func (imp *imports) lookup(path, name string) types.Object { + p := imp.path2pkg[path] + if p == nil { + return nil + } + return p.Scope().Lookup(name) +} + +// findAvailableName returns an available name to import a generated proto +// package as. +// +// We try xpb, x2pb, x3pb, etc. (x stands for expression protobuf, or extra +// protobuf). This way, humans editing the source can recognize the placeholder +// name and replace it with something more descriptive and more inline with the +// respective team style. +func (imp *imports) findAvailableName(path string) string { + if !strings.HasSuffix(path, "go_proto") { + // default name for non proto imports, assumed to be available + return filepath.Base(path) + } + + name := "xpb" + cnt := 2 + for { + if _, ok := imp.renameByName[name]; !ok { + break // name available + } + name = fmt.Sprintf("x%dpb", cnt) + cnt++ + } + imp.renameByName[name] = path + imp.renameByPath[path] = name + return name +}
diff --git a/internal/fix/get.go b/internal/fix/get.go new file mode 100644 index 0000000..f635c5a --- /dev/null +++ b/internal/fix/get.go
@@ -0,0 +1,421 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +// assignGet rewrites a direct scalar field access on the rhs of variable +// definitions, e.g.: +// +// v := m.Field +// => +// var v *fieldType +// if m.HasField() { +// v = proto.Helper(m.GetField()) +// } +func assignGet(c *cursor) { + n := c.Node() + as, ok := n.(*dst.AssignStmt) + if !ok { + c.Logf("ignoring %T (looking for AssignStmt)", n) + return + } + if len(as.Lhs) != 1 { + c.Logf("ignoring: len(Lhs) != 1") + return + } + lhsID, ok := as.Lhs[0].(*dst.Ident) + if !ok { + c.Logf("ignoring lhs %T (looking for Ident)", as.Lhs[0]) + return + } + if len(as.Rhs) != 1 { + c.Logf("ignoring: len(Rhs) != 1") + return + } + if as.Tok != token.DEFINE { + c.Logf("ignoring %v (looking for token.DEFINE)", as.Tok) + return + } + rhs := as.Rhs[0] + if !isPtrToBasic(c.underlyingTypeOf(rhs)) { + c.Logf("ignoring: accessed field is not a scalar field") + return + } + if !c.isSideEffectFree(rhs) { + c.Logf("ignoring: accessor expression is not side effect free") + return + } + field, ok := c.trackedProtoFieldSelector(rhs) + if !ok { + c.Logf("ignoring: rhs is not a proto field selector") + return + } + + lhsExpr := dst.Clone(lhsID).(*dst.Ident) + c.setType(lhsExpr, c.typeOf(lhsID)) + field2 := cloneSelectorExpr(c, field) // for Get + asStmt := &dst.AssignStmt{ + Tok: token.DEFINE, + Lhs: []dst.Expr{lhsExpr}, + } + + if hasNeeded(c, field) { + field3 := cloneSelectorExpr(c, field) // for Get + rhs := valueOrNil(c, + // Intentionally drop node decorations to avoid spurious line breaks + // inside a proto.ValueOrNil() call. + sel2call(c, "Has", field2, nil, dst.NodeDecs{}), + field3, + *n.Decorations()) + // Avoid a line break between return and proto.ValueOrNil(). + rhs.Decorations().Before = dst.None + asStmt.Rhs = append(asStmt.Rhs, rhs) + } else { + asStmt.Rhs = append(asStmt.Rhs, c.newProtoHelperCall(sel2call(c, "Get", field2, nil, *rhs.Decorations()), nil)) + } + moveDecsBeforeStart(asStmt, asStmt.Rhs[0]) + c.ReplaceUnsafe(asStmt, PointerAlias) +} + +// assignGet rewrites a direct scalar field access in a return statement, e.g.: +// +// return m.Field +// => +// if !m.HasField() { +// return nil +// } +// return proto.Helper(m.GetField()) +func returnGet(c *cursor) { + n := c.Node() + rs, ok := n.(*dst.ReturnStmt) + if !ok { + c.Logf("ignoring %T (looking for ReturnStmt)", n) + return + } + // Technically we could handle this case but it is not very common and + // would require some work to properly clone all the nodes and keep + // the types up to date. + if len(rs.Results) != 1 { + c.Logf("ignoring: len(Results) != 1") + return + } + rhs := rs.Results[0] + if !isPtrToBasic(c.underlyingTypeOf(rhs)) { + c.Logf("ignoring: accessed field is not a scalar field") + return + } + if !c.isSideEffectFree(rhs) { + c.Logf("ignoring: accessor expression is not side effect free") + return + } + field, ok := c.trackedProtoFieldSelector(rhs) + if !ok { + c.Logf("ignoring: rhs is not a proto field selector") + return + } + + ret := &dst.ReturnStmt{} + if hasNeeded(c, field) { + // return proto.ValueOrNil(m.HasField(), m.GetField) + field1 := cloneSelectorExpr(c, field) // for Has + field2 := cloneSelectorExpr(c, field) // for Get + + ret.Results = append(ret.Results, valueOrNil(c, + // Intentionally drop node decorations to avoid spurious line breaks + // inside a proto.ValueOrNil() call. + sel2call(c, "Has", field1, nil, dst.NodeDecs{}), + field2, + *n.Decorations())) + } else { + // return proto.Helper(m.GetField()) + field2 := cloneSelectorExpr(c, field) // for Get + ret.Results = append(ret.Results, c.newProtoHelperCall(sel2call(c, "Get", field2, nil, *rhs.Decorations()), nil)) + } + moveDecsBeforeStart(ret, ret.Results[0]) + c.ReplaceUnsafe(ret, PointerAlias) +} + +// Move decorations (line breaks and comments) from src to dest. +func moveDecsBeforeStart(dest, src dst.Node) { + dest.Decorations().Before = src.Decorations().Before + dest.Decorations().Start = src.Decorations().Start + src.Decorations().Before = dst.None + src.Decorations().Start = nil +} + +// getPre rewrites the code to use Get methods. This function is executed by +// traversing the tree in preorder. getPre rewrites assignment and return +// statements that assign/return with exactly one direct scalar field access +// expression. getPost handles all other cases of direct field access rewrites +// that need getter. +func getPre(c *cursor) bool { + if _, ok := c.Parent().(*dst.BlockStmt); !ok { + c.Logf("ignoring node with parent of type %T (looking for BlockStmt)", c.Parent()) + return true + } + if !c.lvl.ge(Yellow) { + return true + } + assignGet(c) + returnGet(c) + return true +} + +// getPost rewrites the code to use Get methods. This function is executed by +// traversing the tree in postorder +func getPost(c *cursor) bool { + // &m.F => proto.Helper(m.GetF()) // proto3 scalars + // &m.F => no rewrite // everything else + if ue, ok := c.Node().(*dst.UnaryExpr); ok && ue.Op == token.AND && c.lvl.ge(Red) { + field, ok := c.trackedProtoFieldSelector(ue.X) + if !ok { + return true + } + if t := c.typeOf(field); isScalar(t) && !isPtrToBasic(t) { + c.ReplaceUnsafe(c.newProtoHelperCall(sel2call(c, "Get", field, nil, *c.Node().Decorations()), t), PointerAlias) + return true + } + markMissingRewrite(field, "address of field") + return true + } + if ue, ok := c.Parent().(*dst.UnaryExpr); ok && ue.Op == token.AND { + return true + } + + if isLValue(c) { + return true + } + n, ok := c.Node().(dst.Expr) + if !ok { + return true + } + + if _, ok := c.Parent().(*dst.IncDecStmt); ok { + return true + } + + // *m.F => m.GetF() for proto2 scalars + if isDeref(n) && isBasic(c.underlyingTypeOf(n)) { + field, ok := c.trackedProtoFieldSelector(dstutil.Unparen(addr(c, n))) + if !ok { + return true + } + c.Replace(sel2call(c, "Get", field, nil, *n.Decorations())) + return true + } + + field, ok := c.trackedProtoFieldSelector(n) + if !ok { + return true + } + + // Oneofs are not fields (members of the oneof union are fields) and should + // not have the Get method. In the open API, oneofs could be used as objects + // of their own which was incompatible with the proto spec. + // + // Hence we have to explicitly ignore those cases. + if isOneof(c.typeOf(field)) { + if c.lvl.ge(Red) { + c.numUnsafeRewritesByReason[OneofFieldAccess]++ + addCommentAbove(c.Parent(), field, "// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md).") + } + return true + } + + // m.F => m.GetF() for all except proto2 scalar fields. + if !isPtrToBasic(c.underlyingTypeOf(n)) { + if isPtr(c.typeOf(field.X)) || c.canAddr(field.X) { + c.Replace(sel2call(c, "Get", field, nil, *n.Decorations())) + } else if c.lvl.ge(Red) { + c.ReplaceUnsafe(sel2call(c, "Get", field, nil, *n.Decorations()), InexpressibleAPIUsage) + } + return true + } + + // for proto2 scalars: + // m.F => m.GetF().Enum() for enums + // m.F => proto.Helper(m.GetF()) otherwise + if c.lvl.ge(Yellow) { // for proto2 scalars we loose aliasing + // Don't do this rewrite: + // *m.F => *proto.Helper(m.GetF()) + // as it rarely makes sense. + // + // We could get here if "*m.F" wasn't rewritten to "m.GetF()" + // for some reason (e.g. we don't rewrite "*m.F++" to "m.GetF()++"). + if _, ok := c.Parent().(*dst.StarExpr); ok { + return true + } + if hasNeeded(c, field) { + c.ReplaceUnsafe(funcLiteralForHas(c, n, field), PointerAlias) + } else { + c.ReplaceUnsafe(sel2call(c, "Get", field, nil, *n.Decorations()), PointerAlias) + } + return true + } + + return true +} + +func funcLiteralForHas(c *cursor, n dst.Expr, field *dst.SelectorExpr) dst.Node { + nodeElemType := c.typeOf(n) + if ptr, ok := nodeElemType.(*types.Pointer); ok { + nodeElemType = ptr.Elem() + } + msgType := c.typeOf(field.X) + + // We need two copies of field. They are identical. + field1 := cloneSelectorExpr(c, field) // for Has + field2 := cloneSelectorExpr(c, field) // for Get + + if c.isSideEffectFree(field.X) { + // Call proto.ValueOrNil() directly, no function literal needed. + return valueOrNil(c, + sel2call(c, "Has", field1, nil, *n.Decorations()), + field2, + *n.Decorations()) + } + + var retElemType dst.Expr = &dst.Ident{Name: nodeElemType.String()} + if named, ok := nodeElemType.(*types.Named); ok { + pkgID := &dst.Ident{Name: c.imports.name(named.Obj().Pkg().Path())} + c.setType(pkgID, types.Typ[types.Invalid]) + pkgSel := &dst.Ident{Name: named.Obj().Name()} + c.setType(pkgSel, types.Typ[types.Invalid]) + retElemType = &dst.SelectorExpr{ + X: pkgID, + Sel: pkgSel, + } + } + c.setType(retElemType, nodeElemType) + retType := &dst.StarExpr{X: retElemType} + c.setType(retType, types.NewPointer(nodeElemType)) + + msgParamSel := c.selectorForProtoMessageType(msgType) + msgParamType := &dst.StarExpr{X: msgParamSel} + c.setType(msgParamType, msgType) + + msgParam := &dst.Ident{Name: "msg"} + c.setType(msgParam, msgType) + field1.X = &dst.Ident{Name: "msg"} + c.setType(field1.X, msgType) + field2.X = &dst.Ident{Name: "msg"} + c.setType(field2.X, msgType) + + untypedNil := &dst.Ident{Name: "nil"} + c.setType(untypedNil, types.Typ[types.UntypedNil]) + + funcLit := &dst.FuncLit{ + // func(msg *pb.M2) <type> { + Type: &dst.FuncType{ + Params: &dst.FieldList{ + List: []*dst.Field{ + &dst.Field{ + Names: []*dst.Ident{msgParam}, + Type: msgParamType, + }, + }, + }, + Results: &dst.FieldList{ + List: []*dst.Field{ + &dst.Field{ + Type: retType, + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: []dst.Stmt{ + // return proto.ValueOrNil(…) + &dst.ReturnStmt{ + Results: []dst.Expr{ + valueOrNil(c, + sel2call(c, "Has", field1, nil, *n.Decorations()), + field2, + *n.Decorations()), + }, + }, + }, + }, + } + // We do not know whether the proto package was imported, so we may not be + // able to construct the correct type signature. Set the type to invalid, + // like we do for any code involving the proto package. + c.setType(funcLit, types.Typ[types.Invalid]) + c.setType(funcLit.Type, types.Typ[types.Invalid]) + + call := &dst.CallExpr{ + Fun: funcLit, + Args: []dst.Expr{ + field.X, + }, + } + c.setType(call, c.typeOf(n)) + + return call +} + +func valueOrNil(c *cursor, has dst.Expr, sel *dst.SelectorExpr, decs dst.NodeDecs) *dst.CallExpr { + fnsel := &dst.Ident{Name: "ValueOrNil"} + get := sel2call(c, "Get", sel, nil, decs) + fn := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(protoImport)}, + Sel: fnsel, + }, + Args: []dst.Expr{ + has, + get.Fun, + }, + } + fn.Decs.NodeDecs = decs + + t := c.underlyingTypeOf(sel.Sel) + var pkg *types.Package + if use := c.objectOf(sel.Sel); use != nil { + pkg = use.Pkg() + } + value := types.NewParam(token.NoPos, pkg, "_", t) + recv := types.NewParam(token.NoPos, pkg, "_", c.underlyingTypeOf(sel.X)) + + getterType := types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false) + getterParam := types.NewParam(token.NoPos, pkg, "_", getterType) + boolParam := types.NewParam(token.NoPos, pkg, "_", types.Typ[types.Bool]) + c.setType(fnsel, types.NewSignature(nil, types.NewTuple(boolParam, getterParam), types.NewTuple(value), false)) + c.setType(fn, t) + + c.setType(fn.Fun, c.typeOf(fnsel)) + + // We set the type for "proto" identifier to Invalid because that's consistent with what the + // typechecker does on new code. We need to distinguish "invalid" type from "no type was + // set" as the code panics on the later in order to catch issues with missing type updates. + c.setType(fn.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid]) + return fn +} + +func isDeref(n dst.Node) bool { + _, ok := n.(*dst.StarExpr) + return ok +} + +// true if c.Node() is on left-hand side of an assignment +func isLValue(c *cursor) bool { + p, ok := c.Parent().(*dst.AssignStmt) + if !ok { + return false + } + for _, ch := range p.Lhs { + if ch == c.Node() { + return true + } + } + return false +}
diff --git a/internal/fix/get_test.go b/internal/fix/get_test.go new file mode 100644 index 0000000..51384ca --- /dev/null +++ b/internal/fix/get_test.go
@@ -0,0 +1,329 @@ +// 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 fix + +import ( + "testing" +) + +func TestGet(t *testing.T) { + tests := []test{{ + desc: "proto2: get message ptr", + in: "_ = m2.M", + want: map[Level]string{ + Green: "_ = m2.GetM()", + }, + }, { + desc: "proto2: get message value", + in: "_ = *m2.M", + want: map[Level]string{ + Green: "_ = *m2.GetM()", + }, + }, { + desc: "proto2: get scalar ptr", + in: `_ = m2.S`, + want: map[Level]string{ + Green: `_ = m2.S`, + Yellow: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`, + Red: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`, + }, + }, { + desc: "proto2: get scalar ptr with comments", + in: ` +// before line +extra := m2.S // end of line +_ = extra +`, + want: map[Level]string{ + Green: ` +// before line +extra := m2.S // end of line +_ = extra +`, + Yellow: ` +// before line +extra := proto.ValueOrNil(m2.HasS(), m2.GetS) // end of line +_ = extra +`, + Red: ` +// before line +extra := proto.ValueOrNil(m2.HasS(), m2.GetS) // end of line +_ = extra +`, + }, + }, { + desc: "proto2: get enum ptr", + in: `_ = m2.E`, + want: map[Level]string{ + Green: `_ = m2.E`, + Yellow: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`, + Red: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`, + }, + }, { + desc: "proto2: get scalar value", + in: "_ = *m2.S", + want: map[Level]string{ + Green: "_ = m2.GetS()", + }, + }, { + desc: "proto2: scalar slice", + in: "_ = m2.Is", + want: map[Level]string{ + Green: "_ = m2.GetIs()", + }, + }, { + desc: "proto2: scalar slice and index", + in: "_ = m2.Is[0]", + want: map[Level]string{ + Green: "_ = m2.GetIs()[0]", + }, + }, { + desc: "proto2: message slice", + in: "_ = m2.Ms", + want: map[Level]string{ + Green: "_ = m2.GetMs()", + }, + }, { + desc: "proto2: message slice and index", + in: "_ = m2.Ms[0]", + want: map[Level]string{ + Green: "_ = m2.GetMs()[0]", + }, + }, { + desc: "proto2: get in function args", + extra: "func g2(*string, string, *pb2.M2, []int32, []*pb2.M2) { }", + in: "g2(m2.S, *m2.S, m2.M, m2.Is, m2.Ms)", + want: map[Level]string{ + Green: "g2(m2.S, m2.GetS(), m2.GetM(), m2.GetIs(), m2.GetMs())", + }, + }, { + desc: "proto3: get message ptr", + in: "_ = m3.M", + want: map[Level]string{ + Green: "_ = m3.GetM()", + }, + }, { + desc: "proto3: get message value", + in: "_ = *m3.M", + want: map[Level]string{ + Green: "_ = *m3.GetM()", + }, + }, { + desc: "proto3: get scalar", + in: "_ = m3.S", + want: map[Level]string{ + Green: "_ = m3.GetS()", + }, + }, { + desc: "field address", + in: ` +_ = &m2.S +_ = &m2.Is +_ = &m2.M +_ = &m2.Ms + +_ = &m3.S +_ = &m3.Is +_ = &m3.M +_ = &m3.Ms +`, + want: map[Level]string{ + Yellow: ` +_ = &m2.S +_ = &m2.Is +_ = &m2.M +_ = &m2.Ms + +_ = &m3.S +_ = &m3.Is +_ = &m3.M +_ = &m3.Ms +`, + Red: ` +_ = &m2.S /* DO_NOT_SUBMIT: missing rewrite for address of field */ +_ = &m2.Is /* DO_NOT_SUBMIT: missing rewrite for address of field */ +_ = &m2.M /* DO_NOT_SUBMIT: missing rewrite for address of field */ +_ = &m2.Ms /* DO_NOT_SUBMIT: missing rewrite for address of field */ + +_ = proto.String(m3.GetS()) +_ = &m3.Is /* DO_NOT_SUBMIT: missing rewrite for address of field */ +_ = &m3.M /* DO_NOT_SUBMIT: missing rewrite for address of field */ +_ = &m3.Ms /* DO_NOT_SUBMIT: missing rewrite for address of field */ +`, + }, + }, { + desc: "proto3: scalar slice", + in: "_ = m3.Is", + want: map[Level]string{ + Green: "_ = m3.GetIs()", + }, + }, { + desc: "proto3: scalar slice and index", + in: "_ = m3.Is[0]", + want: map[Level]string{ + Green: "_ = m3.GetIs()[0]", + }, + }, { + desc: "proto3: message slice", + in: "_ = m3.Ms", + want: map[Level]string{ + Green: "_ = m3.GetMs()", + }, + }, { + desc: "proto3: message slice and index", + in: "_ = m3.Ms[0]", + want: map[Level]string{ + Green: "_ = m3.GetMs()[0]", + }, + }, { + desc: "proto3: get in function args", + extra: "func g3(string, *pb3.M3, []int32, []*pb3.M3) { }", + in: "g3(m3.S, m3.M, m3.Is, m3.Ms)", + want: map[Level]string{ + Green: "g3(m3.GetS(), m3.GetM(), m3.GetIs(), m3.GetMs())", + }, + }, { + desc: "rewriting Get only affects fields", + in: "_ = m2.GetS", + want: map[Level]string{ + Green: "_ = m2.GetS", + Yellow: "_ = m2.GetS", + }, + }, { + desc: "proto2: chained get", + in: "_ = *m2.M.M.M.Ms[0].M.S", + want: map[Level]string{ + Green: "_ = m2.GetM().GetM().GetM().GetMs()[0].GetM().GetS()", + }, + }, { + desc: "proto3: chained get", + in: "_ = m3.M.M.M.Ms[0].M.S", + want: map[Level]string{ + Green: "_ = m3.GetM().GetM().GetM().GetMs()[0].GetM().GetS()", + }, + }} + + runTableTests(t, tests) +} + +func TestNoFuncLiteral(t *testing.T) { + tt := []test{ + { + desc: "return scalar field", + in: ` +_ = func() *float32 { + // before + return m2.F32 // end of line +} +`, + want: map[Level]string{ + Red: ` +_ = func() *float32 { + // before + return proto.ValueOrNil(m2.HasF32(), m2.GetF32) // end of line +} +`, + }, + }, + + { + desc: "return non-scalar fields", + in: ` +_ = func() any { // needs to be any because oneof field types are not exported + return m2.OneofField +} + +_ = func() []byte { + return m2.Bytes +} + +_ = func() *pb2.M2 { + return m2.M +} +`, + want: map[Level]string{ + Red: ` +_ = func() any { // needs to be any because oneof field types are not exported + // DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). + return m2.OneofField +} + +_ = func() []byte { + return m2.GetBytes() +} + +_ = func() *pb2.M2 { + return m2.GetM() +} +`, + }, + }, + + { + desc: "define var from field", + in: ` +f := m2.F32 +_ = f + +of := m2.OneofField +_ = of +`, + want: map[Level]string{ + Red: ` +f := proto.ValueOrNil(m2.HasF32(), m2.GetF32) +_ = f + +// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). +of := m2.OneofField +_ = of +`, + }, + }, + + { + desc: "define var from non-scalar fields", + in: ` +of := m2.OneofField +_ = of + +b := m2.Bytes +_ = b + +m := m2.M +_ = m +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). +of := m2.OneofField +_ = of + +b := m2.GetBytes() +_ = b + +m := m2.GetM() +_ = m +`, + }, + }, + + { + desc: "derefence potential nil pointer", + in: ` +_ = *(m2.I32) + 35 +`, + want: map[Level]string{ + Green: ` +_ = m2.GetI32() + 35 +`, + Red: ` +_ = m2.GetI32() + 35 +`, + }, + }, + } + + runTableTests(t, tt) +}
diff --git a/internal/fix/has.go b/internal/fix/has.go new file mode 100644 index 0000000..1809a33 --- /dev/null +++ b/internal/fix/has.go
@@ -0,0 +1,257 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +// hasPre rewrites comparisons with nil in the following cases: +// - for proto2 optional scalar fields, replace with "!m.HasF()" or "m.HasF()" +// - for proto3 bytes fields, replace with "len(m.GetF()) == 0" or "len(m.GetF()) > 0" +// - for simple conditionals (e.g. "if f := m.F; f != nil {") replace with "if m.HasF()" +// +// The function does not rewrite proto3 message fields, map fields, or repeated +// fields. Those are handled by changing the direct field access to a Get call. +// +// This function is executed by traversing the tree in preorder. +func hasPre(c *cursor) bool { + // Handle a special case that shows up frequently + // + // if f := m.F; f != nil { => if m.HasF() { + // + // This works for singular, scalar fields and avoids red, incorrect rewrites + // like the following: + // + // if f := proto.Helper(m.GetF()); f != nil { + if ifstmt, ok := c.Node().(*dst.IfStmt); ok { + if ifstmt.Init == nil { + return true + } + if ifstmt.Else != nil { + // For now, focus on the most common case. Perhaps we could add handling + // of "else" blocks one day. + return true + } + lhs, op, ok := comparisonWithNil(ifstmt.Cond) + if !ok { + return true + } + condIdent, ok := lhs.(*dst.Ident) + if !ok { + return true + } + // The init statement must define a new name that's used in the comparison + // with nil but is not used as a pointer otherwise. We only handle the + // common case of "f := m.F" for now. + def, ok := ifstmt.Init.(*dst.AssignStmt) + if !ok || def.Tok != token.DEFINE || len(def.Lhs) != 1 || len(def.Rhs) != 1 { + return true + } + rhsSel, ok := def.Rhs[0].(*dst.SelectorExpr) + if !ok { + return true + } + condObj := c.objectOf(condIdent) + if defIdent, ok := def.Lhs[0].(*dst.Ident); !ok || c.objectOf(defIdent) != condObj { + return true + } + if usesAsPointer(c, ifstmt.Body, condObj) { + return true + } + + if hasCall, ok := hasCallForProtoField(c, rhsSel, op, dst.NodeDecs{}); ok { + ifstmt.Init = nil + ifstmt.Cond = hasCall + c.Replace(ifstmt) + dstutil.Apply(ifstmt.Body, nil, func(cur *dstutil.Cursor) bool { + star, ok := cur.Node().(*dst.StarExpr) + if !ok { + return true + } + ident, ok := star.X.(*dst.Ident) + if !ok { + return true + } + if c.objectOf(ident) == condObj { + // Is the pointee assigned to? + + if as, ok := cur.Parent().(*dst.AssignStmt); ok { + var found bool + for _, l := range as.Lhs { + if l == cur.Node() { + // It is easier to replace the pointer + // dereference with a direct field access here + // and to rely on a later pass to rewrite it to + // a setter. The alternative is to replace it + // with a setter directly. + clone := cloneSelectorExpr(c, rhsSel) + star.X = clone + found = true + } + } + if found { + return true + } + } + + // The pointee is used as value. It is safe to use the Getter. + cur.Replace(sel2call(c, "Get", cloneSelectorExpr(c, rhsSel), nil, *rhsSel.Decorations())) + } + return true + }) + } + return true + } + + // Handle conditionals that use a selector on the left-hand side: + // + // m.F != nil => m.HasF() + // m.F == nil => !m.HasF() + if _, _, ok := comparisonWithNil(c.Node()); !ok { + return true + } + expr := c.Node().(*dst.BinaryExpr) + if call, ok := hasCallForProtoField(c, expr.X, expr.Op, *expr.Decorations()); ok { + if sel := expr.X.(*dst.SelectorExpr); !ok || isPtr(c.typeOf(sel.X)) || c.canAddr(sel.X) { + c.Replace(call) + } else if c.lvl.ge(Red) { + c.ReplaceUnsafe(call, InexpressibleAPIUsage) + } + return false + } + field, ok := c.trackedProtoFieldSelector(expr.X) + if !ok { + return true + } + if s, ok := c.typeOf(field).(*types.Slice); ok { + // use "len" for proto3 bytes fields. + if bt, ok := s.Elem().(*types.Basic); ok && bt.Kind() == types.Byte { + // m.F == nil => len(m.GetF()) == 0 + // m.F != nil => len(m.GetF()) != 0 + var getVal dst.Expr = field + if isPtr(c.typeOf(field.X)) || c.canAddr(field.X) { + getVal = sel2call(c, "Get", field, nil, dst.NodeDecs{}) + } + lenCall := &dst.CallExpr{ + Fun: dst.NewIdent("len"), + Args: []dst.Expr{getVal}, + } + c.setType(lenCall, types.Typ[types.Int]) + c.setType(lenCall.Fun, types.Universe.Lookup("len").Type()) + op := token.EQL + if expr.Op == token.NEQ { + op = token.NEQ + } + zero := &dst.BasicLit{Kind: token.INT, Value: "0"} + c.setType(zero, types.Typ[types.Int]) + bop := &dst.BinaryExpr{ + X: lenCall, + Op: op, + Y: zero, + Decs: expr.Decs, + } + c.setType(bop, types.Typ[types.Bool]) + c.Replace(bop) + return true + } + } + + // We don't handle repeated fields and maps explicitly here. We handle those + // cases by rewriting the code to use Get calls: + // + // m.F == nil => m.GetF() == nil + // m.F != nil => m.GetF() != nil + // + // We depend on the above and on the implementation detail that after: + // + // m.SetF(nil) + // + // we guarantee: + // + // m.GetF() == nil + // + // This works and preserves the old API behavior. However, it's + // a discouraged pattern in new code. It's better to check the + // length instead. + // + // We DO NOT do that as it couldn't be a green rewrite due to + // the difference between nil and zero-length slices. + + return true +} + +// usesAsPointer returns whether the pointer target is used without being +// dereferenced. +func usesAsPointer(c *cursor, b *dst.BlockStmt, target types.Object) bool { + var out bool + dstutil.Apply(b, nil, func(cur *dstutil.Cursor) bool { + // Is current node a usage of target without dereferencing it? + if ident, ok := cur.Node().(*dst.Ident); ok && c.objectOf(ident) == target && !isStarExpr(cur.Parent()) { + out = true + return false // terminate traversal immediately + } + return true + }) + return out +} + +// hasCallForProtoField returns a "has" call for the given proto field selector, x. +// +// For example, for "m.F", it returns "m.HasF()". The op determines the context +// in which "m.F" is used. Only "==" and "!=" have an effect here, with the +// expectation that "x" is used as "m.F OP nil" +func hasCallForProtoField(c *cursor, x dst.Expr, op token.Token, decs dst.NodeDecs) (hasCall dst.Expr, ok bool) { + field, ok := c.trackedProtoFieldSelector(x) + if !ok { + return nil, false + } + if !c.useClearOrHas(field) { + return nil, false + } + call := sel2call(c, "Has", field, nil, decs) + if op == token.EQL { + // m.F == nil => !m.HasF() + return not(c, call), true + } else if op == token.NEQ { + // m.F != nil => m.HasF() + return call, true + } + return nil, false +} + +// comparisonWithNil checks that n is a comparison with nil. If so, it returns +// the left-hand side and the comparison operator. Otherwise, it returns false. +func comparisonWithNil(n dst.Node) (lhs dst.Expr, op token.Token, ok bool) { + x, ok := n.(*dst.BinaryExpr) + if !ok { + return nil, 0, false + } + if x.Op != token.EQL && x.Op != token.NEQ { + return nil, 0, false + } + if ident, ok := x.Y.(*dst.Ident); !ok || ident.Name != "nil" { + return nil, 0, false + } + return x.X, x.Op, true +} + +func isStarExpr(x dst.Node) bool { + _, ok := x.(*dst.StarExpr) + return ok +} + +func not(c *cursor, expr dst.Expr) dst.Expr { + out := &dst.UnaryExpr{ + Op: token.NOT, + X: expr, + } + c.setType(out, c.underlyingTypeOf(expr)) + return out +}
diff --git a/internal/fix/has_test.go b/internal/fix/has_test.go new file mode 100644 index 0000000..22789fb --- /dev/null +++ b/internal/fix/has_test.go
@@ -0,0 +1,603 @@ +// 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 fix + +import ( + "testing" +) + +func TestAvoidRedundantHaser(t *testing.T) { + tests := []test{ + { + desc: "basic non-nil check", + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.I32 != nil { + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m.SetI32(m2.GetI32()) +} +`, + }, + }, + + { + desc: "basic haser check", + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.HasI32() { + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m.SetI32(m2.GetI32()) +} +`, + }, + }, + + { + desc: "modification after has check", + extra: `func f() *int32 { return nil }`, + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.I32 = f() + m.I32 = m2.I32 +} + +if m2.I32 != nil { + m2.I32 = f() + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.I32 = f() + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } else { + m.ClearI32() + } +} + +if m2.HasI32() { + m2.I32 = f() + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } else { + m.ClearI32() + } +} +`, + }, + }, + + { + desc: "shadowing", + extra: `func f() *pb2.M2 { return nil }`, + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.HasI32() { + m2 := f() + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m2 := f() + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } else { + m.ClearI32() + } +} +`, + }, + }, + + { + desc: "usage in lhs of assignment", + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.I32 = proto.Int32(int32(42)) + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.SetI32(int32(42)) + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } else { + m.ClearI32() + } +} +`, + }, + }, + + { + desc: "usage of different field", + srcfiles: []string{"pkg.go"}, + in: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.S = proto.String("Hello") + m.I32 = m2.I32 +} + +if m2.HasI32() { + m2.SetS("Hello") + m.I32 = m2.I32 +} +`, + want: map[Level]string{ + Green: ` +m := &pb2.M2{} +if m2.HasI32() { + m2.SetS("Hello") + m.SetI32(m2.GetI32()) +} + +if m2.HasI32() { + m2.SetS("Hello") + m.SetI32(m2.GetI32()) +} +`, + }, + }, + + { + desc: "comp literal (non-test)", + srcfiles: []string{"pkg.go"}, + in: ` +if m2.HasI32() { + m := &pb2.M2{ + I32: m2.I32, + } + _ = m +} + +if m2.I32 != nil { + m := &pb2.M2{ + I32: m2.I32, + } + _ = m +} +`, + want: map[Level]string{ + Red: ` +if m2.HasI32() { + m := &pb2.M2{} + m.SetI32(m2.GetI32()) + _ = m +} + +if m2.HasI32() { + m := &pb2.M2{} + m.SetI32(m2.GetI32()) + _ = m +} +`, + }, + }, + + { + desc: "comp literal (test)", + srcfiles: []string{"pkg_test.go"}, + in: ` +if m2.HasI32() { + m := &pb2.M2{ + I32: m2.I32, + } + _ = m +} + +if m2.I32 != nil { + m := &pb2.M2{ + I32: m2.I32, + } + _ = m +} +`, + want: map[Level]string{ + Red: ` +if m2.HasI32() { + m := pb2.M2_builder{ + I32: m2.GetI32(), + }.Build() + _ = m +} + +if m2.HasI32() { + m := pb2.M2_builder{ + I32: m2.GetI32(), + }.Build() + _ = m +} +`, + }, + }, + + { + desc: "return", + srcfiles: []string{"pkg.go"}, + in: ` +_ = func() *int32 { + if m2.HasI32() { + return m2.I32 + } + return nil +} + +_ = func() int32 { + if m2.HasI32() { + return *m2.I32 + } + return int32(0) +} +`, + want: map[Level]string{ + Red: ` +_ = func() *int32 { + if m2.HasI32() { + return proto.Int32(m2.GetI32()) + } + return nil +} + +_ = func() int32 { + if m2.HasI32() { + return m2.GetI32() + } + return int32(0) +} +`, + }, + }, + + { + desc: "assign", + srcfiles: []string{"pkg.go"}, + in: ` +if m2.HasI64() { + i := m2.I64 + _ = i +} +`, + + want: map[Level]string{ + Red: ` +if m2.HasI64() { + i := proto.Int64(m2.GetI64()) + _ = i +} +`, + }, + }, + + { + desc: "getter", + srcfiles: []string{"pkg.go"}, + in: ` +if m2.GetF32() != 0 { + m := &pb2.M2{} + m.F32 = m2.F32 + _ = m +} +if *m2.F32 != 0 { + m := &pb2.M2{} + m.F32 = m2.F32 + _ = m +} +`, + + want: map[Level]string{ + Green: ` +if m2.GetF32() != 0 { + m := &pb2.M2{} + m.SetF32(m2.GetF32()) + _ = m +} +if m2.GetF32() != 0 { + m := &pb2.M2{} + m.SetF32(m2.GetF32()) + _ = m +} +`, + }, + }, + + { + desc: "getter: bytes", + srcfiles: []string{"pkg.go"}, + in: ` +if m2.GetBytes() != nil { + m := &pb2.M2{} + m.Bytes = m2.Bytes + _ = m +} +if nil != m2.Bytes { + m := &pb2.M2{} + m.Bytes = m2.Bytes + _ = m +} +`, + + want: map[Level]string{ + Green: ` +if m2.GetBytes() != nil { + m := &pb2.M2{} + if x := m2.GetBytes(); x != nil { + m.SetBytes(x) + } + _ = m +} +if nil != m2.GetBytes() { + m := &pb2.M2{} + if x := m2.GetBytes(); x != nil { + m.SetBytes(x) + } + _ = m +} +`, + }, + }, + + { + desc: "getter: different field", + srcfiles: []string{"pkg.go"}, + in: ` +if *m2.I64 != 0 { + m := &pb2.M2{} + m.I32 = m2.I32 + _ = m +} +if 0 != m2.GetI64() { + m := &pb2.M2{} + m.I32 = m2.I32 + _ = m +} +`, + + want: map[Level]string{ + Green: ` +if m2.GetI64() != 0 { + m := &pb2.M2{} + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } + _ = m +} +if 0 != m2.GetI64() { + m := &pb2.M2{} + if m2.HasI32() { + m.SetI32(m2.GetI32()) + } + _ = m +} +`, + }, + }, + } + runTableTests(t, tests) +} + +func TestHas(t *testing.T) { + tests := []test{{ + desc: "proto2: if-has", + in: ` +if e := m2.E; e != nil { + _ = *e + _ = *e + _ = *e +} + +if e := m2.E; e == nil { + _ = *e +} + +// New name must be used in the conditional. +var f *int +if e := m2.E; f != nil { + _ = *e +} + +// We don't apply this rewrite when comparison with nil is ok. +if m := m2.GetM(); m != nil { + _ = m +} + +// The code can't use the pointer value. +if e := m2.E; e != nil { + _ = e +} +`, + want: map[Level]string{ + Green: ` +if m2.HasE() { + _ = m2.GetE() + _ = m2.GetE() + _ = m2.GetE() +} + +if !m2.HasE() { + _ = m2.GetE() +} + +// New name must be used in the conditional. +var f *int +if e := m2.E; f != nil { + _ = *e +} + +// We don't apply this rewrite when comparison with nil is ok. +if m := m2.GetM(); m != nil { + _ = m +} + +// The code can't use the pointer value. +if e := m2.E; e != nil { + _ = e +} +`, + }}, { + desc: "proto2: has", + in: ` +_ = m2.E != nil +_ = m2.B != nil +_ = m2.Bytes != nil +_ = m2.F32 != nil +_ = m2.F64 != nil +_ = m2.I32 != nil +_ = m2.I64 != nil +_ = m2.Ui32 != nil +_ = m2.Ui64 != nil +_ = m2.M != nil +_ = m2.Is != nil +_ = m2.Ms != nil +_ = m2.Map != nil +`, + want: map[Level]string{ + Green: ` +_ = m2.HasE() +_ = m2.HasB() +_ = m2.HasBytes() +_ = m2.HasF32() +_ = m2.HasF64() +_ = m2.HasI32() +_ = m2.HasI64() +_ = m2.HasUi32() +_ = m2.HasUi64() +_ = m2.HasM() +_ = m2.GetIs() != nil +_ = m2.GetMs() != nil +_ = m2.GetMap() != nil +`}}, { + desc: "proto2: doesn't have", + in: ` +_ = m2.E == nil +_ = m2.B == nil +_ = m2.Bytes == nil +_ = m2.F32 == nil +_ = m2.F64 == nil +_ = m2.I32 == nil +_ = m2.I64 == nil +_ = m2.Ui32 == nil +_ = m2.Ui64 == nil +_ = m2.M == nil +_ = m2.Is == nil +_ = m2.Ms == nil +_ = m2.Map == nil +`, + want: map[Level]string{ + Green: ` +_ = !m2.HasE() +_ = !m2.HasB() +_ = !m2.HasBytes() +_ = !m2.HasF32() +_ = !m2.HasF64() +_ = !m2.HasI32() +_ = !m2.HasI64() +_ = !m2.HasUi32() +_ = !m2.HasUi64() +_ = !m2.HasM() +_ = m2.GetIs() == nil +_ = m2.GetMs() == nil +_ = m2.GetMap() == nil +`}}, { + desc: "proto3: has", + in: ` +_ = m3.Bytes != nil +_ = m3.M != nil +_ = m3.Is != nil +_ = m3.Ms != nil +_ = m3.Map != nil +`, + want: map[Level]string{ + Green: ` +_ = len(m3.GetBytes()) != 0 +_ = m3.HasM() +_ = m3.GetIs() != nil +_ = m3.GetMs() != nil +_ = m3.GetMap() != nil +`}}, { + desc: "proto3: doesn't have", + in: ` +_ = m3.Bytes == nil +_ = m3.M == nil +_ = m3.Is == nil +_ = m3.Ms == nil +_ = m3.Map == nil +`, + want: map[Level]string{ + Green: ` +_ = len(m3.GetBytes()) == 0 +_ = !m3.HasM() +_ = m3.GetIs() == nil +_ = m3.GetMs() == nil +_ = m3.GetMap() == nil +`}}, { + desc: "proto3 value: has", + in: ` +var m3val pb3.M3 +_ = m3val.Bytes != nil +_ = m3val.M != nil +_ = m3val.Is != nil +_ = m3val.Ms != nil +_ = m3val.Map != nil +`, + want: map[Level]string{ + Green: ` +var m3val pb3.M3 +_ = len(m3val.GetBytes()) != 0 +_ = m3val.HasM() +_ = m3val.GetIs() != nil +_ = m3val.GetMs() != nil +_ = m3val.GetMap() != nil +`}}, { + desc: "proto3 value: doesn't have", + in: ` +var m3val pb3.M3 +_ = m3val.Bytes == nil +_ = m3val.M == nil +_ = m3val.Is == nil +_ = m3val.Ms == nil +_ = m3val.Map == nil +`, + want: map[Level]string{ + Green: ` +var m3val pb3.M3 +_ = len(m3val.GetBytes()) == 0 +_ = !m3val.HasM() +_ = m3val.GetIs() == nil +_ = m3val.GetMs() == nil +_ = m3val.GetMap() == nil +`}}, + } + + runTableTests(t, tests) +}
diff --git a/internal/fix/hasneeded.go b/internal/fix/hasneeded.go new file mode 100644 index 0000000..4bc8e9e --- /dev/null +++ b/internal/fix/hasneeded.go
@@ -0,0 +1,271 @@ +// 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 fix + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/dave/dst" +) + +// isGetterFor return true if expr is a getter for the proto field sel +func isGetterFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool { + call, ok := expr.(*dst.CallExpr) + if !ok { + return false + } + dstSel, ok := call.Fun.(*dst.SelectorExpr) + if !ok { + return false + } + if id, ok := dstSel.X.(*dst.Ident); !ok || c.objectOf(id) != c.objectOf(sel.X.(*dst.Ident)) { + return false + } + if dstSel.Sel.Name != "Get"+sel.Sel.Name { + return false + } + return true +} + +// isDirectFieldAccess return true if expr is a dereferencing direct field +// access for the proto field sel +func isDirectFieldAccess(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool { + se, ok := expr.(*dst.StarExpr) + if !ok { + return false + } + otherSel, ok := se.X.(*dst.SelectorExpr) + if !ok { + return false + } + otherID, ok := otherSel.X.(*dst.Ident) + if !ok { + return false + } + if c.objectOf(otherID) != c.objectOf(sel.X.(*dst.Ident)) { + return false + } + if c.objectOf(otherSel.Sel) != c.objectOf(sel.Sel) { + return false + } + return true +} + +// isFieldAccessFor returns true if expr is a field access for sel (either +// getter of direct). +func isFieldAccessFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool { + return isGetterFor(c, expr, sel) || isDirectFieldAccess(c, expr, sel) +} + +// guaranteesExistenceOf return true if the expr is a boolean expression that +// guarantees that sel is set, e.g.: +// +// if m.GetX() != "" { +func guaranteesExistenceOf(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool { + _, ok := sel.X.(*dst.Ident) + if !ok { + return false + } + fieldType := c.typeOf(sel) + if pt, ok := fieldType.(*types.Pointer); ok { + fieldType = pt.Elem() + } + // If expr a binary condition we check if it is a comparison against nil + // or if it's conjunction and either of the branches is a haser. + if bin, ok := expr.(*dst.BinaryExpr); ok { + if bin.Op == token.LAND { + return guaranteesExistenceOf(c, bin.X, sel) || guaranteesExistenceOf(c, bin.Y, sel) + } + if bin.Op == token.NEQ { + // Is this `if m.GetX() != 0 {` ? + // (0 is representative for the type specific zero value) + foundZero := false + expr := bin.X + if isScalarTypeZeroExpr(c, fieldType, expr) { + foundZero = true + expr = bin.Y + } + if !foundZero && !isScalarTypeZeroExpr(c, fieldType, bin.Y) { + return false + } + return isFieldAccessFor(c, expr, sel) + + } + return false + + } + return false +} + +// isHaserFor return true if the expr is a boolean expression that +// implements a has-check for the message field specified by sel. +func isHaserFor(c *cursor, expr dst.Expr, sel *dst.SelectorExpr) bool { + xID, ok := sel.X.(*dst.Ident) + if !ok { + return false + } + xObj := c.objectOf(xID) + selObj := c.objectOf(sel.Sel) + + // If expr a binary condition we check if it is a comparison against nil + // or if it's conjunction and either of the branches is a haser. + if bin, ok := expr.(*dst.BinaryExpr); ok { + if bin.Op == token.LAND { + return isHaserFor(c, bin.X, sel) || isHaserFor(c, bin.Y, sel) + } + if bin.Op == token.NEQ { + // Is this `if m.X != nil {` ? + // Or `if nil != m.X {` ? + foundNil := false + expr := bin.X + if c.typeOf(expr) == types.Typ[types.UntypedNil] { + foundNil = true + expr = bin.Y + } + if !foundNil && c.typeOf(bin.Y) != types.Typ[types.UntypedNil] { + return false + } + bSel, ok := expr.(*dst.SelectorExpr) + if !ok { + return false + } + bID, ok := bSel.X.(*dst.Ident) + if !ok { + return false + } + if c.objectOf(bID) != xObj || c.objectOf(bSel.Sel) != selObj { + return false + } + return true + } + return false + + } + + // Is this `if m.HasX() {` ? + call, ok := expr.(*dst.CallExpr) + if !ok { + return false + } + dstSel, ok := call.Fun.(*dst.SelectorExpr) + if !ok { + return false + } + if id, ok := dstSel.X.(*dst.Ident); !ok || c.typesInfo.objectOf(id) != xObj { + return false + } + if dstSel.Sel.Name != "Has"+sel.Sel.Name { + return false + } + + return true +} + +// isScalarTypeZeroExpr returns true if e is a zero value for t +func isScalarTypeZeroExpr(c *cursor, t types.Type, e dst.Expr) bool { + if _, ok := t.(*types.Basic); !isBytes(t) && !isEnum(t) && !ok { + return false + } + zeroExpr := scalarTypeZeroExpr(c, t) + if id0, ok := zeroExpr.(*dst.Ident); ok { + if id1, ok := e.(*dst.Ident); ok { + return id0.Name == id1.Name + } + } + if bl0, ok := zeroExpr.(*dst.BasicLit); ok { + if bl1, ok := e.(*dst.BasicLit); ok { + // floats can be compared to either 0 or 0.0 + // scalarTypeZeroExpr generates the more specific 0.0 + // but we would like to allow comparison against 0 as + // well. + if bl0.Kind == token.FLOAT && bl1.Value == "0" { + return true + } + return bl0.Value == bl1.Value && bl0.Kind == bl1.Kind + } + } + return false +} + +// hasNeeded implements a basic dataflow analysis to find out if the scope +// enclosing sel guarantees that sel is set (non-nil). We consider this +// guaranteed if there is either a `sel != nil` or a `${sel.X}.Has${sel.Sel}()` +// condition and the field is not modified afterwards. +// In the absence of bugs, this check never produces false-positives but it may +// produce false-negatives. +func hasNeeded(c *cursor, sel *dst.SelectorExpr) bool { + selX, ok := sel.X.(*dst.Ident) + if !ok { + return true + } + + innerMost, opener := c.enclosingASTStmt(sel) + if _, ok := opener.(*ast.IfStmt); !ok { + return true + } + dstIf, ok := c.typesInfo.dstMap[opener] + if !ok { + c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v (was c.typesInfo.dstMap not updated across rewrites?)", opener, opener) + return true + } + + cond := dstIf.(*dst.IfStmt).Cond + if !isHaserFor(c, cond, sel) && !guaranteesExistenceOf(c, cond, sel) { + return true + } + + enclosing, ok := c.typesInfo.dstMap[innerMost] + if !ok { + c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost) + return true + } + lastSeen := false + usageFound := false + + xObj := c.objectOf(selX) + selObj := c.objectOf(sel.Sel) + var visit visitorFunc + visit = func(n dst.Node) dst.Visitor { + if lastSeen { + return nil + } + + if se, ok := n.(*dst.SelectorExpr); ok && se == sel { + lastSeen = true + // Skip recursing into children; all subsequent visit() calls + // will return immediately. + return nil + } + + if as, ok := n.(*dst.AssignStmt); ok { + // Is the field that was checked assigned to? + for _, lhs := range as.Lhs { + if usesObject(c, lhs, xObj) && usesObject(c, lhs, selObj) { + usageFound = true + return nil + } + } + } + + // Access is okay if it's not a setter for the field + // and if it is not assigned to (checked above). + if doesNotModifyField(c, n, sel.Sel.Name) { + return nil + } + + if id, ok := n.(*dst.Ident); ok && c.objectOf(id) == xObj { + c.Logf("found non-proto-field-selector usage of %q", id.Name) + usageFound = true + return nil + } + + return visit // recurse into children + } + dst.Walk(visit, enclosing) + + return !lastSeen || usageFound +}
diff --git a/internal/fix/incdec.go b/internal/fix/incdec.go new file mode 100644 index 0000000..524f02a --- /dev/null +++ b/internal/fix/incdec.go
@@ -0,0 +1,51 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" +) + +func incDecPre(c *cursor) bool { + stmt, ok := c.Node().(*dst.IncDecStmt) + if !ok { + return true + } + x := stmt.X + if pe, ok := x.(*dst.ParenExpr); ok { + x = pe.X + } + if se, ok := x.(*dst.StarExpr); ok { + x = se.X + } + field, ok := c.trackedProtoFieldSelector(x) + if !ok { + return true + } + if !c.isSideEffectFree(field) { + markMissingRewrite(stmt, "inc/dec statement") + return true + } + val := &dst.BinaryExpr{ + X: sel2call(c, "Get", cloneSelectorExpr(c, field), nil, *field.Decorations()), + Y: dst.NewIdent("1"), + } + c.setType(val.Y, types.Typ[types.UntypedInt]) + c.setType(val, c.typeOf(field)) + if stmt.Tok == token.INC { + val.Op = token.ADD + } else { + val.Op = token.SUB + } + // Not handled: decorations from the inner nodes (ParenExpr, + // StarExpr). While those are unlikely to be there, we would ideally not + // lose those. Perhaps we need a more general solution instead of handling + // decorations on a case-by-case basis. + c.Replace(c.expr2stmt(sel2call(c, "Set", field, val, *stmt.Decorations()), field)) + return true +}
diff --git a/internal/fix/incdec_test.go b/internal/fix/incdec_test.go new file mode 100644 index 0000000..ae5c4de --- /dev/null +++ b/internal/fix/incdec_test.go
@@ -0,0 +1,103 @@ +// 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 fix + +import ( + "testing" +) + +func TestIncDecUnary(t *testing.T) { + tests := []test{ + { + desc: "unary expressions: contexts", + extra: `var x struct { M *pb2.M2 }`, + in: ` +*m2.I32++ +(*m2.I32)++ +*m2.M.I32++ +(*m2.M.I32)++ + +*x.M.I32++ + +*m2.I32-- +`, + want: map[Level]string{ + Green: ` +m2.SetI32(m2.GetI32() + 1) +m2.SetI32(m2.GetI32() + 1) +m2.GetM().SetI32(m2.GetM().GetI32() + 1) +m2.GetM().SetI32(m2.GetM().GetI32() + 1) + +x.M.SetI32(x.M.GetI32() + 1) + +m2.SetI32(m2.GetI32() - 1) +`, + }, + }, + + { + desc: "unary expressions: proto3", + in: ` +m3.I32++ +(m3.I32)++ +m3.M.I32++ +(m3.M.I32)++ +m3.I32-- +`, + want: map[Level]string{ + Green: ` +m3.SetI32(m3.GetI32() + 1) +m3.SetI32(m3.GetI32() + 1) +m3.GetM().SetI32(m3.GetM().GetI32() + 1) +m3.GetM().SetI32(m3.GetM().GetI32() + 1) +m3.SetI32(m3.GetI32() - 1) +`, + }, + }, + + { + desc: "unary expressions: decorations", + in: ` +// hello +*m2.I32++ // world +`, + want: map[Level]string{ + Green: ` +// hello +m2.SetI32(m2.GetI32() + 1) // world +`, + }, + }, + + { + desc: "unary expressions: no duplicated side-effects", + extra: ` +func f2() *pb2.M2 { return nil } +func f3() *pb3.M3 { return nil } +`, + in: ` +*f2().I32++ +(*f2().I32)++ +f3().I32++ +(f3().I32)++ +`, + want: map[Level]string{ + Green: ` +*f2().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +(f2().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +f3().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +(f3().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +`, + Red: ` +*f2().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +(f2().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +f3().I32++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +(f3().GetI32())++ /* DO_NOT_SUBMIT: missing rewrite for inc/dec statement */ +`, + }, + }} + + runTableTests(t, tests) +}
diff --git a/internal/fix/naming.go b/internal/fix/naming.go new file mode 100644 index 0000000..f6717fb --- /dev/null +++ b/internal/fix/naming.go
@@ -0,0 +1,61 @@ +// 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 fix + +import ( + "go/types" + "strings" + "unicode" + + log "github.com/golang/glog" +) + +func helperVarNameForType(t types.Type) string { + // Get to the elementary type (pb.M2) if this is a pointer type (*pb.M2). + elem := t + if ptr, ok := elem.(*types.Pointer); ok { + elem = ptr.Elem() + } + named, ok := elem.(*types.Named) + if !ok { + log.Fatalf("BUG: proto message unexpectedly not a named type (but %T)?!", elem) + } + return helperVarNameForName(named.Obj().Name()) +} + +// helperVarNameForName produces a name for a helper variable for the specified +// package-local name. +func helperVarNameForName(packageLocal string) string { + fullName := strings.ToLower(packageLocal[:1]) + packageLocal[1:] + if len(fullName) < 10 { + return fullName + } + // The name is too long for a helper variable. Abbreviate if possible. + if strings.Contains(fullName, "_") { + // Split along the underscores and use the first letter of each word, + // turning BigtableRowMutationArgs_Mod_SetCell into bms. + parts := strings.Split(fullName, "_") + abbrev := "" + for _, part := range parts { + abbrev += strings.ToLower(part[:1]) + } + return abbrev + } + if parts := strings.FieldsFunc(packageLocal, unicode.IsLower); len(parts) > 1 { + // We split around the lowercase characters, leaving us with only the + // uppercase characters. + return strings.ToLower(strings.Join(parts, "")) + } else if len(parts) == 1 && len(parts[0]) > 1 { + // The name starts with multiple uppercase letters, but is followed by + // only lowercase letters (e.g. ESDimensions). Return all the uppercase + // letters we have. + return strings.ToLower(parts[0]) + } + // The name is too long and cannot be abbreviated based on uppercase + // letters. Cut off at the closest vowel. + return strings.TrimRightFunc(fullName[:10], func(r rune) bool { + return r == 'a' || r == 'e' || r == 'i' || r == 'o' || r == 'u' + }) +}
diff --git a/internal/fix/naming_test.go b/internal/fix/naming_test.go new file mode 100644 index 0000000..96722ca --- /dev/null +++ b/internal/fix/naming_test.go
@@ -0,0 +1,46 @@ +// 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 fix + +import "testing" + +func TestHelperVarNameForName(t *testing.T) { + for _, tt := range []struct { + name string + want string + }{ + { + name: "M2", + want: "m2", + }, + + { + name: "BigtableRowMutationArgs_Mod_SetCell", + want: "bms", + }, + + { + name: "BigtableRowMutationArgs", + want: "brma", + }, + + { + name: "Verylongonewordnamethatcannotbeabbreviated", + want: "verylongon", + }, + + { + name: "ESDimensions", + want: "esd", + }, + } { + t.Run(tt.name, func(t *testing.T) { + got := helperVarNameForName(tt.name) + if got != tt.want { + t.Errorf("helperVarNameForName(%q) = %q, want %q", tt.name, got, tt.want) + } + }) + } +}
diff --git a/internal/fix/oneof.go b/internal/fix/oneof.go new file mode 100644 index 0000000..07c1fc2 --- /dev/null +++ b/internal/fix/oneof.go
@@ -0,0 +1,497 @@ +// 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 fix + +import ( + "context" + "go/token" + "go/types" + "sort" + + "github.com/dave/dst" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/protodetecttypes" +) + +// generateOneofBuilderCases transforms oneofs in composite literals to be +// compatible with builders that don't have a field for the oneof but a field +// for each case instead. The RHS is either a direct field access or getter +// oneof field. The only other values that could be assigned to the oneof field +// would be the wrapper types which must be handled before this function is +// called. +// +// &pb.M{ +// OneofField: m2.OneofField, +// } +// => +// &pb.M{ +// OneofField: m2.OneofField, +// BytesOneof: m2.GetBytesOneof(), +// IntOneof: func(msg *pb2.M2) *int64 { +// if !msg.HasIntOneof() { +// return nil +// } +// return proto.Int64(msg.GetIntOneof()) +// }(m2), +// } +// +// Removal of the oneof KeyValueExpr happens later. +// Changing from &pb.M to pb.M_builder{...}.Build() happens in another step. +// +// Note: The exact results differ and depend on the valid cases for the oneof +// field. For scalar fields, we have to use self invoking function literals to +// be able to check if the case is unset and pass nil to the builder field if +// so. +func generateOneofBuilderCases(c *cursor, updates []func(), lit *dst.CompositeLit, kv *dst.KeyValueExpr) ([]func(), bool) { + t := c.typesInfo.typeOf(kv.Value) + if !isOneof(t) { + c.Logf("returning: not a oneof field") + return nil, false + } + c.Logf("try generating oneof cases...") + // First we need to collect an exhaustive list of alternatives. We do this + // by collecting all wrapper types for the given field that are defined in + // the generated proto package. + nt, ok := t.(*types.Named) + if !ok { + c.Logf("ignoring oneof of type %T (looking for *types.Named)", c.Node()) + return nil, false + } + pkg := nt.Obj().Pkg() + + targetID := pkg.Path() + + p, err := loader.LoadOne(context.Background(), c.loader, &loader.Target{ID: targetID}) + if err != nil { + c.Logf("failed to load proto package %q: %v", pkg.Path(), err) + return nil, false + } + + // Get the message name from which the oneof field is taken + rhsSel, ok := kv.Value.(*dst.SelectorExpr) + if !ok { + // If it is not a direct field access, it must be a call expression, + // i.e. the getter for the oneof field. + // For now we don't support anything else (e.g. variables). + callExpr, ok := kv.Value.(*dst.CallExpr) + if !ok { + c.Logf("ignoring value %T (looking for SelectorExpr or CallExpr)", kv.Value) + return nil, false + } + rhsSel, ok = callExpr.Fun.(*dst.SelectorExpr) + if !ok { + c.Logf("ignoring value %T (looking for SelectorExpr)", callExpr.Fun) + return nil, false + } + } + + // Oneofs are guaranteed to have *types.Interface as underlying type. + it := t.Underlying().(*types.Interface) + type typeAndName struct { + name string + typ *types.Struct + } + // Collect wrapper types to generate an exhaustive list of cases + var wrapperTypes []typeAndName + for _, idt := range p.TypeInfo.Defs { + // Some things (like `package p`) are toplevel definitions that don't + // have an associated object. + if idt == nil { + continue + } + // All wrapper types are exported *types.Named + if !idt.Exported() { + continue + } + nt, ok := idt.Type().(*types.Named) + if !ok { + continue + } + if !types.Implements(types.NewPointer(nt), it) { + continue + } + // All wrapper types are structs with exactly one field. + // The one field is named that same as the case itself. + st := nt.Underlying().(*types.Struct) + name := st.Field(0).Name() + wrapperTypes = append(wrapperTypes, typeAndName{name, st}) + } + + sort.Slice(wrapperTypes, func(i, j int) bool { + return wrapperTypes[i].name < wrapperTypes[j].name + }) + + first := true + for _, tan := range wrapperTypes { + caseName := tan.name + + // Generate Value + st := tan.typ + if st.NumFields() != 1 { + c.Logf("wrapper type has %d fields (expected 1)", st.NumFields()) + return nil, false + } + fieldType := st.Field(0).Type() + + var rhsExpr dst.Expr + if _, ok = fieldType.Underlying().(*types.Basic); ok { + rhsExpr = funcLiteralForOneofField(c, rhsSel, caseName, fieldType) + } else { + rhsExpr = oneOfSelector(c, "Get", caseName, rhsSel.X, fieldType, nil, *rhsSel.Decorations()) + } + + // Generate KeyValueExpr + nKey := &dst.Ident{Name: caseName} + c.setType(nKey, types.NewPointer(fieldType)) + nKeyVal := &dst.KeyValueExpr{ + Key: nKey, + Value: rhsExpr, + } + // Duplicate the decorations to all children. + if first { + nKeyVal.Decs = kv.Decs + first = false + } + nKeyVal.Decorations().After = dst.NewLine + c.setType(nKeyVal, types.Typ[types.Invalid]) + c.Logf("generated KeyValueExpr for %v", caseName) + updates = append(updates, func() { + lit.Elts = append(lit.Elts, nKeyVal) + }) + } + + c.Logf("generated %d oneof cases", len(wrapperTypes)) + return updates, true +} + +// funcLiteralForOneofField is similar to funcLiteralForHas but does not operate +// on existing (Go struct) fields but on proto message fields. Such fields don't +// exist in the Go type system and thus we cannot reuse funcLiteralForHas. +func funcLiteralForOneofField(c *cursor, field *dst.SelectorExpr, fieldName string, fieldType types.Type) *dst.CallExpr { + msg := field.X.(*dst.Ident) + msgType := c.typeOf(msg) + + getSel := &dst.Ident{ + Name: "Get" + fixConflictingNames(msgType, "Get", fieldName), + } + getX := cloneIdent(c, msg) + getCall := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: getX, + Sel: getSel, + }, + } + value := types.NewParam(token.NoPos, nil, "_", fieldType) + recv := types.NewParam(token.NoPos, nil, "_", c.underlyingTypeOf(msg)) + c.setType(getSel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false)) + c.setType(getCall.Fun, c.typeOf(getSel)) + c.setType(getCall, fieldType) + + hasSel := &dst.Ident{ + Name: "Has" + fixConflictingNames(msgType, "Has", fieldName), + } + hasX := &dst.Ident{Name: "msg"} + c.setType(hasX, types.Typ[types.Invalid]) + hasCall := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: hasX, + Sel: hasSel, + }, + } + c.setType(hasSel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.Typ[types.Bool])), false)) + c.setType(hasCall.Fun, c.typeOf(hasSel)) + c.setType(hasCall, types.Typ[types.Bool]) + + // oneof fields are synthetically generated, so there are no node + // decorations to carry over. + var emptyDecs dst.NodeDecs + if c.isSideEffectFree(msg) { + // Call proto.ValueOrNil() directly, no function literal needed. + hasX.Name = msg.Name + field2 := cloneSelectorExpr(c, field) + field2.Sel.Name = fieldName + return valueOrNil(c, hasCall, field2, emptyDecs) + } + + var retElemType dst.Expr = &dst.Ident{Name: fieldType.String()} + fieldTypePtr := types.NewPointer(fieldType) + c.setType(retElemType, fieldType) + retType := &dst.StarExpr{X: retElemType} + c.setType(retType, fieldTypePtr) + + msgTypePtr := types.NewPointer(msgType) + msgParamSel := c.selectorForProtoMessageType(msgType) + msgParamType := &dst.StarExpr{X: msgParamSel} + c.setType(msgParamType, msgTypePtr) + + msgParam := &dst.Ident{Name: "msg"} + c.setType(msgParam, types.Typ[types.Invalid]) + + field2 := cloneSelectorExpr(c, field) + field2.Sel.Name = fieldName + funcLit := &dst.FuncLit{ + // func(msg *pb.M2) <type> { + Type: &dst.FuncType{ + Params: &dst.FieldList{ + List: []*dst.Field{ + &dst.Field{ + Names: []*dst.Ident{msgParam}, + Type: msgParamType, + }, + }, + }, + Results: &dst.FieldList{ + List: []*dst.Field{ + &dst.Field{ + Type: retType, + }, + }, + }, + }, + Body: &dst.BlockStmt{ + List: []dst.Stmt{ + // return proto.ValueOrNil(…) + &dst.ReturnStmt{ + Results: []dst.Expr{ + valueOrNil(c, hasCall, field2, emptyDecs), + }, + }, + }, + }, + } + // We do not know whether the proto package was imported, so we may not be + // able to construct the correct type signature. Set the type to invalid, + // like we do for any code involving the proto package. + c.setType(funcLit, types.Typ[types.Invalid]) + c.setType(funcLit.Type, types.Typ[types.Invalid]) + + msgArg := dst.Clone(msg).(dst.Expr) + c.setType(msgArg, msgType) + call := &dst.CallExpr{ + Fun: funcLit, + Args: []dst.Expr{ + msgArg, + }, + } + c.setType(call, fieldTypePtr) + + return call +} + +// This is like sel2call but for oneof fields which are not actual (Go struct) +// fields in the generated Go struct and thus we cannot use sel2call directly +func oneOfSelector(c *cursor, prefix, fieldName string, msg dst.Expr, fieldType types.Type, val dst.Expr, decs dst.NodeDecs) *dst.CallExpr { + name := fixConflictingNames(c.typeOf(msg), prefix, fieldName) + fnsel := &dst.Ident{ + Name: prefix + name, + } + selX := dst.Clone(msg).(dst.Expr) + c.setType(selX, types.Typ[types.Invalid]) + fn := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: selX, + Sel: fnsel, + }, + } + if val != nil { + fn.Args = []dst.Expr{val} + } + + value := types.NewParam(token.NoPos, nil, "_", fieldType) + recv := types.NewParam(token.NoPos, nil, "_", c.typeOf(msg)) + switch prefix { + case "Get": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false)) + c.setType(fn, fieldType) + case "Set": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(value), types.NewTuple(), false)) + c.setVoidType(fn) + case "Clear": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(), false)) + c.setVoidType(fn) + case "Has": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, nil, "_", types.Typ[types.Bool])), false)) + c.setType(fn, types.Typ[types.Bool]) + default: + panic("bad function name prefix '" + prefix + "'") + } + c.setType(fn.Fun, c.typeOf(fnsel)) + return fn +} + +// destructureOneofWrapper returns K (field name), V (assigned value), and +// typeof(K) (type of the field) for a oneof wrapper expression +// +// "&OneofWrapper{K: V}" +// +// and equivalents that omit either K, or V, or both. +func destructureOneofWrapper(c *cursor, x dst.Expr) (string, types.Type, dst.Expr, *dst.NodeDecs, bool) { + c.Logf("destructuring one of wrapper") + ue, ok := x.(*dst.UnaryExpr) + if !ok || ue.Op != token.AND { + return oneofWrapperSelector(c, x) + } + clit, ok := ue.X.(*dst.CompositeLit) + if !ok { + return oneofWrapperSelector(c, x) + } + s, ok := c.underlyingTypeOf(clit).(*types.Struct) + if !ok || s.NumFields() != 1 { + return oneofWrapperSelector(c, x) + } + if !isOneofWrapper(c, clit) { + return oneofWrapperSelector(c, x) + } + var decs *dst.NodeDecs + var val dst.Expr + switch { + case len(clit.Elts) > 1: + panic("oneof wrapper clit has multiple elements") + case len(clit.Elts) == 0: // &Oneof{} + val = nil + case isKV(clit.Elts[0]): // &Oneof{K: V} + // We are about to replace clit.Elts[0], so propagate its decorations. + decs = clit.Elts[0].Decorations() + val = clit.Elts[0].(*dst.KeyValueExpr).Value + default: // &Oneof{V} + val = clit.Elts[0] + // Propagate the decorations and clear them at the value level. + var decsCopy dst.NodeDecs + decsCopy = *val.Decorations() + decs = &decsCopy + val.Decorations().Before = dst.None + val.Decorations().After = dst.None + val.Decorations().Start = nil + val.Decorations().End = nil + } + if ident, ok := val.(*dst.Ident); ok && ident.Name == "nil" { + val = nil + } + + valType := s.Field(0).Type() + if isBytes(valType) && !isNeverNilSliceExpr(c, val) { + if !c.lvl.ge(Yellow) { + c.numUnsafeRewritesByReason[IncompleteRewrite]++ + c.Logf("ignoring: rewrite level smaller than Yellow") + return "", nil, nil, nil, false + } + if val == nil { + id := &dst.Ident{ + Name: "byte", + } + c.setType(id, valType.(*types.Slice).Elem()) + typ := &dst.ArrayType{ + Elt: id, + } + c.setType(typ, valType) + val = &dst.CompositeLit{ + Type: typ, + } + c.setType(val, valType) + return s.Field(0).Name(), valType, val, decs, true + } + c.numUnsafeRewritesByReason[MaybeOneofChange]++ + // NOTE(lassefolger): This ValueOrDefaultBytes() call is only + // necessary in builders, but we don’t have enough context in + // this part of the code to omit it for setters. + return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefaultBytes", val), decs, true + } + isMsgOneof := false + if ptr, ok := valType.(*types.Pointer); ok && (protodetecttypes.Type{T: ptr.Elem()}.IsMessage()) { + isMsgOneof = true + } + if isMsgOneof && val != nil && !isNeverNilExpr(c, val) { + if !c.lvl.ge(Yellow) { + c.numUnsafeRewritesByReason[IncompleteRewrite]++ + c.Logf("ignoring: rewrite level smaller than Yellow") + return "", nil, nil, nil, false + } + c.numUnsafeRewritesByReason[MaybeOneofChange]++ + return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefault", val), decs, true + } + + return s.Field(0).Name(), valType, val, decs, true +} + +func oneofWrapperSelector(c *cursor, x dst.Expr) (string, types.Type, dst.Expr, *dst.NodeDecs, bool) { + var decs *dst.NodeDecs + if !c.lvl.ge(Yellow) { + c.Logf("ignoring: rewrite level smaller than Yellow") + return "", nil, nil, nil, false + } + if !isOneofWrapper(c, x) { + c.Logf("ignoring: not a one of wrapper") + return "", nil, nil, nil, false + } + if !isNeverNilExpr(c, x) { + if c.lvl.le(Yellow) { + c.Logf("ignoring: potential nil expression and rewrite level not Red") + // This could be handled with self calling func literals but + // we should only do so if there is a significant number of + // locations that need this. + c.numUnsafeRewritesByReason[IncompleteRewrite]++ + return "", nil, nil, nil, false + } + c.numUnsafeRewritesByReason[MaybeNilPointerDeref]++ + } + // It is possible that this rewrite unsets the oneof field where it was + // previously set to a type but without value (which is not a valid + // proto message), e.g.: + // + // m.OneofField = pb.OneofWrapper{nil} + c.numUnsafeRewritesByReason[MaybeOneofChange]++ + + t := c.underlyingTypeOf(x) + if p, ok := t.(*types.Pointer); ok { + t = p.Elem().Underlying() + } + s := t.(*types.Struct) + val := &dst.SelectorExpr{ + X: x, + Sel: &dst.Ident{ + Name: s.Field(0).Name(), + }, + } + valType := s.Field(0).Type() + c.setType(val.Sel, valType) + c.setType(val, s.Field(0).Type()) + if ptr, ok := valType.(*types.Pointer); ok && (protodetecttypes.Type{T: ptr.Elem()}.IsMessage()) { + return s.Field(0).Name(), valType, valueOrDefault(c, "ValueOrDefault", val), decs, true + } + return s.Field(0).Name(), s.Field(0).Type(), val, decs, true +} + +func isKV(x dst.Expr) bool { + _, ok := x.(*dst.KeyValueExpr) + return ok +} + +const protooneofdefaultImport = "google.golang.org/protobuf/protooneofdefault" + +func valueOrDefault(c *cursor, fun string, val dst.Expr) dst.Expr { + fnsel := &dst.Ident{Name: fun} + fn := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(protooneofdefaultImport)}, + Sel: fnsel, + }, + Args: []dst.Expr{ + val, + }, + } + + t := c.underlyingTypeOf(val) + value := types.NewParam(token.NoPos, nil, "_", t) + c.setType(fnsel, types.NewSignature(nil, types.NewTuple(value), types.NewTuple(value), false)) + c.setType(fn, t) + + c.setType(fn.Fun, c.typeOf(fnsel)) + + // We set the type for "proto" identifier to Invalid because that's consistent with what the + // typechecker does on new code. We need to distinguish "invalid" type from "no type was + // set" as the code panics on the later in order to catch issues with missing type updates. + c.setType(fn.Fun.(*dst.SelectorExpr).X, types.Typ[types.Invalid]) + return fn +}
diff --git a/internal/fix/oneof_test.go b/internal/fix/oneof_test.go new file mode 100644 index 0000000..bc5f60b --- /dev/null +++ b/internal/fix/oneof_test.go
@@ -0,0 +1,1484 @@ +// 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 fix + +import ( + "testing" +) + +func TestOneof(t *testing.T) { + tests := []test{{ + desc: "clear", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +m2.OneofField = nil +`, + want: map[Level]string{ + Green: ` +m2.ClearOneofField() +`, + }, + }, { + desc: "multiassign", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `var ignored string`, + in: ` +m2.OneofField, ignored = &pb2.M2_StringOneof{StringOneof: "hello"}, "" +`, + want: map[Level]string{ + Green: ` +m2.OneofField, ignored = &pb2.M2_StringOneof{StringOneof: "hello"}, "" +`, + }, + }, { + desc: "ignore variable oneof", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +oneofField := m2.OneofField +m2.OneofField = oneofField +`, + want: map[Level]string{ + Green: ` +oneofField := m2.OneofField +m2.OneofField = oneofField +`, + Red: ` +// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). +oneofField := m2.OneofField +// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). +m2.OneofField = oneofField +`, + }, + }, { + desc: "has", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +_ = m2.OneofField != nil +_ = m2.OneofField == nil +_ = m3.OneofField != nil +_ = m3.OneofField == nil +if m2.OneofField != nil { +} +if m2.OneofField == nil { +} +if m3.OneofField != nil { +} +if m3.OneofField == nil { +} +if o := m2.OneofField; o != nil { +} +if o := m2.OneofField; o == nil { +} +if o := m3.OneofField; o != nil { +} +if o := m3.OneofField; o == nil { +} +`, + want: map[Level]string{ + Red: ` +_ = m2.HasOneofField() +_ = !m2.HasOneofField() +_ = m3.HasOneofField() +_ = !m3.HasOneofField() +if m2.HasOneofField() { +} +if !m2.HasOneofField() { +} +if m3.HasOneofField() { +} +if !m3.HasOneofField() { +} +if m2.HasOneofField() { +} +if !m2.HasOneofField() { +} +if m3.HasOneofField() { +} +if !m3.HasOneofField() { +} +`, + }, + }, { + desc: "proto3 works", // Basic smoke test; proto3 is handled together with proto2. + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +m3.OneofField = &pb3.M3_StringOneof{StringOneof: "hello"} +m3.OneofField = &pb3.M3_EnumOneof{EnumOneof: pb3.M3_E_VAL} +m3.OneofField = &pb3.M3_MsgOneof{MsgOneof: &pb3.M3{}} +m3.OneofField = nil +_ = m3.OneofField != nil +`, + want: map[Level]string{ + Green: ` +m3.SetStringOneof("hello") +m3.SetEnumOneof(pb3.M3_E_VAL) +m3.SetMsgOneof(&pb3.M3{}) +m3.ClearOneofField() +_ = m3.HasOneofField() +`, + }, + }, { + desc: "build naming", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +m3.OneofField = &pb3.M3_Build{Build: 1} +m3.OneofField = &pb3.M3_Build{1} +m3.OneofField = &pb3.M3_Build{} +`, + want: map[Level]string{ + Green: ` +m3.SetBuild_(1) +m3.SetBuild_(1) +m3.SetBuild_(0) +`, + }, + }, { + desc: "oneof: simple type switch, field", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch m2.OneofField.(type) { +case *pb2.M2_StringOneof, + *pb2.M2_IntOneof: +case *pb2.M2_MsgOneof: +case *pb2.M2_EnumOneof: +case *pb2.M2_BytesOneof: +case nil: +default: +} + +switch m3.OneofField.(type) { +case *pb3.M3_StringOneof, *pb3.M3_IntOneof: +case *pb3.M3_MsgOneof: +case *pb3.M3_EnumOneof: +case *pb3.M3_BytesOneof, nil: +default: +} +`, + want: map[Level]string{ + Green: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case, + pb2.M2_IntOneof_case: +case pb2.M2_MsgOneof_case: +case pb2.M2_EnumOneof_case: +case pb2.M2_BytesOneof_case: +case 0: +default: +} + +switch m3.WhichOneofField() { +case pb3.M3_StringOneof_case, pb3.M3_IntOneof_case: +case pb3.M3_MsgOneof_case: +case pb3.M3_EnumOneof_case: +case pb3.M3_BytesOneof_case, 0: +default: +} +`, + }, + }, { + desc: "oneof: type switch with naming conflicts", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch m3.OneofField.(type) { +case *pb3.M3_String_: +case *pb3.M3_Reset_: +case *pb3.M3_ProtoMessage_: +case *pb3.M3_Descriptor_: +default: +} +`, + want: map[Level]string{ + Green: ` +switch m3.WhichOneofField() { +case pb3.M3_String__case: +case pb3.M3_Reset__case: +case pb3.M3_ProtoMessage__case: +case pb3.M3_Descriptor__case: +default: +} +`, + }, + }, { + desc: "oneof: type switch with in-file naming conflicts", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +co := &pb2.ConflictingOneof{} +switch co.Included.(type) { +case *pb2.ConflictingOneof_Sub_: +default: +} +`, + want: map[Level]string{ + Green: ` +co := &pb2.ConflictingOneof{} +switch co.WhichIncluded() { +case pb2.ConflictingOneof_Sub_case: +default: +} +`, + }, + }, { + desc: "oneof: type switch with in-file naming conflicts in nested sub-message", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +co := &pb2.ConflictingOneof_DeepSub{} +switch co.DeeplyIncluded.(type) { +case *pb2.ConflictingOneof_DeepSub_Sub_: +default: +} +`, + want: map[Level]string{ + Green: ` +co := &pb2.ConflictingOneof_DeepSub{} +switch co.WhichDeeplyIncluded() { +case pb2.ConflictingOneof_DeepSub_Sub_case: +default: +} +`, + }, + }, { + desc: "oneof: simple type switch, method", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch m2.GetOneofField().(type) { +case *pb2.M2_StringOneof, *pb2.M2_IntOneof: +case *pb2.M2_MsgOneof: +case *pb2.M2_EnumOneof: +case *pb2.M2_BytesOneof: +case nil: +default: +} + +switch m3.GetOneofField().(type) { +case *pb3.M3_StringOneof, *pb3.M3_IntOneof: +case *pb3.M3_MsgOneof: +case *pb3.M3_EnumOneof: +case *pb3.M3_BytesOneof: +case nil: +default: +} +`, + want: map[Level]string{ + Green: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case, pb2.M2_IntOneof_case: +case pb2.M2_MsgOneof_case: +case pb2.M2_EnumOneof_case: +case pb2.M2_BytesOneof_case: +case 0: +default: +} + +switch m3.WhichOneofField() { +case pb3.M3_StringOneof_case, pb3.M3_IntOneof_case: +case pb3.M3_MsgOneof_case: +case pb3.M3_EnumOneof_case: +case pb3.M3_BytesOneof_case: +case 0: +default: +} +`, + }, + }, { + desc: "oneof: type switch decorations", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch /* hello */ m2.OneofField.(type) /* world */ { // ! +case /* A */ *pb2.M2_StringOneof /* B */ : /* C */ +} + +switch /* hello */ m2.GetOneofField().(type) /* world */ { // ! +case /* A */ *pb2.M2_StringOneof /* B */ : /* C */ +} +`, + want: map[Level]string{ + Green: ` +switch /* hello */ m2.WhichOneofField() /* world */ { // ! +case /* A */ pb2.M2_StringOneof_case /* B */ : /* C */ +} + +switch /* hello */ m2.WhichOneofField() /* world */ { // ! +case /* A */ pb2.M2_StringOneof_case /* B */ : /* C */ +} +`, + }, + }, { + desc: "oneof: default non printf like func", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func randomFunc(*pb2.M2, ...interface{}) { }`, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +default: + randomFunc(m2, oneofField) +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +default: + randomFunc(m2, oneofField) +} +`, + Red: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +default: + randomFunc(m2, oneofField) +} +`, + }, + }, { + desc: "oneof: default with T verb", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +default: + fmtErrorf("%T", oneofField) +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +default: + fmtErrorf("%v", oneofField) +} +`, + Red: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +default: + fmtErrorf("%v", oneofField) +} +`, + }, + }, { + desc: "oneof: default with multiple verbs", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +default: + fmtErrorf("%v %T", m2, oneofField) +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +default: + fmtErrorf("%v %v", m2, oneofField) +} +`, + Red: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +default: + fmtErrorf("%v %v", m2, oneofField) +} +`, + }, + }, { + desc: "oneof: with assignment, field access", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +default: + fmtErrorf("%v", oneofField) +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +default: + fmtErrorf("%v", oneofField) +} +`, + }, + }, { + desc: "oneof: with assignment, field access, proto3", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m3.OneofField.(type) { +case *pb3.M3_MsgOneof: + _ = oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m3.WhichOneofField() { +case pb3.M3_MsgOneof_case: + _ = m3.GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, getter access", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +default: + fmtErrorf("%v", oneofField) +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +default: + fmtErrorf("%v", oneofField) +} +`, + }, + }, { + desc: "oneof: with assignment, getter access, in --types_to_update_file", + srcfiles: []string{"code.go", "pkg_test.go"}, + typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2": true}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m2.WhichOneofField() { +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, getter access, not in --types_to_update_file", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + typesToUpdate: map[string]bool{"google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3": true}, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + }, + }, { + desc: "oneof: with assignment, shadowing, non-oneof", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `type O struct {StringOneof int}`, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = m2.GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, shadowing, oneof, nop in green", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: ` +type O struct { + StringOneof string ` + "`protobuf:\"oneof\"`" + ` +}`, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = oneofField.MsgOneof.GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, shadowing, oneof, red is risky", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: ` +type O struct { + StringOneof string ` + "`protobuf:\"oneof\"`" + ` +}`, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Red: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + { + oneofField := &O{} + _ = oneofField.StringOneof + } + _ = m2.GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, field access, selector", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.M.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m2.GetM().WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = m2.GetM().GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetM().GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, field access, call", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.GetM().OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m2.GetM().WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = m2.GetM().GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetM().GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment, field access, other expr; red marker", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `var msgs []*pb2.M2`, + in: ` +switch oneofField := msgs[0].OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +} +`, + want: map[Level]string{ + Red: ` +switch msgs[0].WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = msgs[0].GetStringOneof() +} +`, + }, + }, { + desc: "oneof: with assignment, field access, other expr; green nop", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `var msgs []*pb2.M2`, + in: ` +switch oneofField := msgs[0].OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +} +`, + want: map[Level]string{ + Green: ` +switch msgs[0].WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = msgs[0].GetStringOneof() +} +`, + }, + }, { + desc: "oneof: with assignment, method call", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_MsgOneof: + _ = *oneofField.MsgOneof.S +} +`, + want: map[Level]string{ + Green: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = m2.GetStringOneof() +case pb2.M2_MsgOneof_case: + _ = m2.GetMsgOneof().GetS() +} +`, + }, + }, { + desc: "oneof: with assignment; avoid extra side effects", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() *pb2.M2 { return nil }`, + in: ` +switch oneofField := f().M.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch xmsg := f().GetM(); xmsg.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = xmsg.GetStringOneof() +case pb2.M2_IntOneof_case: + _ = xmsg.GetIntOneof() +} +`, + }, + }, { + desc: "oneof: with assignment; avoid extra side effects; call", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() *pb2.M2 { return nil }`, + in: ` +switch oneofField := f().GetM().GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch xmsg := f().GetM(); xmsg.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = xmsg.GetStringOneof() +case pb2.M2_IntOneof_case: + _ = xmsg.GetIntOneof() +} +`, + }, + }, { + desc: "oneof: with assignment; avoid extra side effects; f", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() *pb2.M2 { return nil }`, + in: ` +switch oneofField := f().GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch xmsg := f(); xmsg.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = xmsg.GetStringOneof() +case pb2.M2_IntOneof_case: + _ = xmsg.GetIntOneof() +} +`, + }, + }, { + desc: "oneof: with assignment; don't override the init", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() *pb2.M2 { return nil }`, + in: ` +switch x := 1; oneofField := f().M.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof + _ = x +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Red: ` +switch x := 1; oneofField := f().GetM().GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof + _ = x +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} /* DO_NOT_SUBMIT: missing rewrite for type switch with side effects and init statement */ +`, + }, + }, { + desc: "oneof: with assignment; don't override the init; green nop", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() *pb2.M2 { return nil }`, + in: ` +switch x := 1; oneofField := f().M.GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof + _ = x +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch x := 1; oneofField := f().GetM().GetOneofField().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof + _ = x +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + }, + }, { + desc: "oneof: with assignment; avoid extra side effects; no oneof", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func f() interface{} { return nil }`, + in: ` +switch oneofField := f().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} + `, + want: map[Level]string{ + Red: ` +switch oneofField := f().(type) { +case *pb2.M2_StringOneof: + _ = oneofField.StringOneof +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + }, + }, { + desc: "oneof: with assignment; don't rewrite unrelated statement", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +default: + var err error + _ = err.Error() +} +`, + want: map[Level]string{ + Red: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = oneofField +case pb2.M2_IntOneof_case: + _ = m2.GetIntOneof() +default: + var err error + _ = err.Error() +} +`, + }, + }, { + desc: "oneof: with assignment; oneof refs in case", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + }, + }, { + desc: "oneof: with assignment; oneof refs in default", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +default: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Green: ` +switch oneofField := m2.OneofField.(type) { +default: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + }, + }, { + desc: "oneof: with assignment; oneof refs in case; red", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_StringOneof: + _ = oneofField +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +} +`, + want: map[Level]string{ + Red: ` +switch m2.WhichOneofField() { +case pb2.M2_StringOneof_case: + _ = oneofField +case pb2.M2_IntOneof_case: + _ = m2.GetIntOneof() +} +`, + }, + }, { + desc: "oneof: with assignment; oneof refs in default; red", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_IntOneof: + _ = oneofField.IntOneof +default: + _ = oneofField +} +`, + want: map[Level]string{ + Red: ` +switch m2.WhichOneofField() { +case pb2.M2_IntOneof_case: + _ = m2.GetIntOneof() +default: + _ = oneofField +} +`, + }, + }, { + desc: "oneof: with assignment; default %v", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + in: ` +switch oneofField := m2.OneofField.(type) { +default: + fmtErrorf("bad oneof %v", oneofField) + fmtErrorf("bad oneof %v", m2.GetOneofField()) + fmtErrorf("bad oneof %v", m2.OneofField) +} + `, + want: map[Level]string{ + Green: ` +switch oneofField := m2.WhichOneofField(); oneofField { +default: + fmtErrorf("bad oneof %v", oneofField) + fmtErrorf("bad oneof %v", m2.WhichOneofField()) + fmtErrorf("bad oneof %v", m2.WhichOneofField()) +} +`, + }, + }} + + runTableTests(t, tests) +} + +func TestOneofBuilder(t *testing.T) { + tests := []test{ + { + desc: "bytes builder", + srcfiles: []string{"pkg_test.go"}, + extra: `var bytes []byte`, + in: ` +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte("hello")}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: []byte{}}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: bytes}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{[]byte("hello")}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{bytes}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{nil}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: nil}} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = pb2.M2_builder{BytesOneof: []byte{}}.Build() +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: bytes}} +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{bytes}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{nil}} +_ = &pb2.M2{OneofField: &pb2.M2_BytesOneof{BytesOneof: nil}} +`, + Red: ` +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = pb2.M2_builder{BytesOneof: []byte{}}.Build() +_ = pb2.M2_builder{BytesOneof: protooneofdefault.ValueOrDefaultBytes(bytes)}.Build() +_ = pb2.M2_builder{BytesOneof: []byte("hello")}.Build() +_ = pb2.M2_builder{BytesOneof: protooneofdefault.ValueOrDefaultBytes(bytes)}.Build() +_ = pb2.M2_builder{BytesOneof: []byte{}}.Build() +_ = pb2.M2_builder{BytesOneof: []byte{}}.Build() +_ = pb2.M2_builder{BytesOneof: []byte{}}.Build() +`, + }, + }, + } + runTableTests(t, tests) +} + +func TestOneofSetter(t *testing.T) { + tests := []test{{ + desc: "basic", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: ` +var str string +var num int64 +`, + in: ` +m2.OneofField = &pb2.M2_StringOneof{StringOneof: "hello"} +m2.OneofField = &pb2.M2_StringOneof{StringOneof: str} +m2.OneofField = &pb2.M2_StringOneof{"hello"} +m2.OneofField = &pb2.M2_StringOneof{str} +m2.OneofField = &pb2.M2_StringOneof{} +m2.OneofField = &pb2.M2_IntOneof{IntOneof: 42} +m2.OneofField = &pb2.M2_IntOneof{IntOneof: num} +m2.OneofField = &pb2.M2_IntOneof{42} +m2.OneofField = &pb2.M2_IntOneof{num} +m2.OneofField = &pb2.M2_IntOneof{} +`, + want: map[Level]string{ + Green: ` +m2.SetStringOneof("hello") +m2.SetStringOneof(str) +m2.SetStringOneof("hello") +m2.SetStringOneof(str) +m2.SetStringOneof("") +m2.SetIntOneof(42) +m2.SetIntOneof(num) +m2.SetIntOneof(42) +m2.SetIntOneof(num) +m2.SetIntOneof(0) +`, + }, + }, { + desc: "enum", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `var enum pb2.M2_Enum`, + in: ` +m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: pb2.M2_E_VAL} +m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: enum} +m2.OneofField = &pb2.M2_EnumOneof{enum} +m2.OneofField = &pb2.M2_EnumOneof{} +m2.OneofField = &pb2.M2_EnumOneof{EnumOneof: 0} +m2.OneofField = &pb2.M2_EnumOneof{0} +`, + want: map[Level]string{ + Green: ` +m2.SetEnumOneof(pb2.M2_E_VAL) +m2.SetEnumOneof(enum) +m2.SetEnumOneof(enum) +m2.SetEnumOneof(0) +m2.SetEnumOneof(0) +m2.SetEnumOneof(0) +`, + }, + }, { + desc: "bytes", + srcfiles: []string{"code.go", "pkg_test.go"}, + extra: `var bytes []byte`, + in: ` +m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: []byte("hello")} +m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: bytes} +m2.OneofField = &pb2.M2_BytesOneof{[]byte("hello")} +m2.OneofField = &pb2.M2_BytesOneof{bytes} +m2.OneofField = &pb2.M2_BytesOneof{} +m2.OneofField = &pb2.M2_BytesOneof{nil} +m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: nil} +`, + want: map[Level]string{ + Green: ` +m2.SetBytesOneof([]byte("hello")) +m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: bytes} +m2.SetBytesOneof([]byte("hello")) +m2.OneofField = &pb2.M2_BytesOneof{bytes} +m2.OneofField = &pb2.M2_BytesOneof{} +m2.OneofField = &pb2.M2_BytesOneof{nil} +m2.OneofField = &pb2.M2_BytesOneof{BytesOneof: nil} +`, + Red: ` +m2.SetBytesOneof([]byte("hello")) +m2.SetBytesOneof(protooneofdefault.ValueOrDefaultBytes(bytes)) +m2.SetBytesOneof([]byte("hello")) +m2.SetBytesOneof(protooneofdefault.ValueOrDefaultBytes(bytes)) +m2.SetBytesOneof([]byte{}) +m2.SetBytesOneof([]byte{}) +m2.SetBytesOneof([]byte{}) +`, + }, + }, { + desc: "nontest: message clit empty", + srcfiles: []string{"code.go"}, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{}} +m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{}} +m2.OneofField = &pb2.M2_MsgOneof{} +m2.OneofField = &pb2.M2_MsgOneof{nil} +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: nil} +`, + want: map[Level]string{ + Green: ` +m2.SetMsgOneof(&pb2.M2{}) +m2.SetMsgOneof(&pb2.M2{}) +m2.OneofField = &pb2.M2_MsgOneof{} +m2.OneofField = &pb2.M2_MsgOneof{nil} +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: nil} +`, + }, + }, { + desc: "nontest: message clit", + srcfiles: []string{"code.go"}, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{B: proto.Bool(true)}} +m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{B: proto.Bool(true)}} +`, + want: map[Level]string{ + // We could detect that this is safe in theory but it + // requires data flow analysis to prove that m2h2 and + // m2h3 are never nil + Green: ` +m2h2 := &pb2.M2{} +m2h2.SetB(true) +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: m2h2} +m2h3 := &pb2.M2{} +m2h3.SetB(true) +m2.OneofField = &pb2.M2_MsgOneof{m2h3} +`, + Yellow: ` +m2h2 := &pb2.M2{} +m2h2.SetB(true) +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h2)) +m2h3 := &pb2.M2{} +m2h3.SetB(true) +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h3)) +`, + }, + }, { + desc: "test: message clit", + srcfiles: []string{"pkg_test.go"}, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: &pb2.M2{B: proto.Bool(true)}} +m2.OneofField = &pb2.M2_MsgOneof{&pb2.M2{B: proto.Bool(true)}} +`, + want: map[Level]string{ + Green: ` +m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build()) +m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build()) +`, + }, + }, { + desc: "test: message clit with decorations", + srcfiles: []string{"pkg_test.go"}, + in: ` +_ = &pb2.M2{ + OneofField: &pb2.M2_MsgOneof{ + // Comment above MsgOneof + MsgOneof: &pb2.M2{ + // Comment above B + B: proto.Bool(true), // end of B line + }, // end of MsgOneof line + }, +} + +_ = &pb2.M2{ + OneofField: &pb2.M2_MsgOneof{ + // Comment above MsgOneof + &pb2.M2{ + // Comment above B + B: proto.Bool(true), // end of B line + }, // end of MsgOneof line + }, +} +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ + // Comment above MsgOneof + MsgOneof: pb2.M2_builder{ + // Comment above B + B: proto.Bool(true), // end of B line + }.Build(), // end of MsgOneof line +}.Build() + +_ = pb2.M2_builder{ + // Comment above MsgOneof + MsgOneof: pb2.M2_builder{ + // Comment above B + B: proto.Bool(true), // end of B line + }.Build(), // end of MsgOneof line +}.Build() +`, + }, + }, { + desc: "nontest: nested message clit with decorations", + srcfiles: []string{"pkg.go"}, + in: ` +_ = &pb2.M2{ + OneofField: &pb2.M2_MsgOneof{ + // Comment above MsgOneof + MsgOneof: &pb2.M2{ + // Comment above B + B: proto.Bool(true), // end of B line + }, // end of MsgOneof line + }, +} +`, + want: map[Level]string{ + // We could detect that this is safe in theory but it + // requires data flow analysis to prove that m2h2 is + // never nil + Green: ` +m2h2 := &pb2.M2{} +// Comment above B +m2h2.SetB(true) // end of B line +m2h3 := &pb2.M2{} +m2h3.OneofField = &pb2.M2_MsgOneof{ + // Comment above MsgOneof + MsgOneof: m2h2, // end of MsgOneof line +} +_ = m2h3 +`, + Yellow: ` +m2h2 := &pb2.M2{} +// Comment above B +m2h2.SetB(true) // end of B line +m2h3 := &pb2.M2{} +// Comment above MsgOneof +m2h3.SetMsgOneof(protooneofdefault.ValueOrDefault(m2h2)) // end of MsgOneof line +_ = m2h3 +`, + }, + }, { + desc: "nontest message clit var", + srcfiles: []string{"code.go"}, + extra: `var msg *pb2.M2`, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg} +m2.OneofField = &pb2.M2_MsgOneof{msg} +`, + want: map[Level]string{ + Green: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg} +m2.OneofField = &pb2.M2_MsgOneof{msg} +`, + Yellow: ` +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg)) +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg)) +`, + }, + }, { + desc: "message clit var", + srcfiles: []string{"pkg_test.go"}, + extra: `var msg *pb2.M2`, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg} +m2.OneofField = &pb2.M2_MsgOneof{msg} +`, + want: map[Level]string{ + Green: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: msg} +m2.OneofField = &pb2.M2_MsgOneof{msg} +`, + Yellow: ` +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg)) +m2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg)) +`, + }, + }, { + desc: "message builder", + srcfiles: []string{"code.go", "pkg_test.go"}, + in: ` +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: pb2.M2_builder{B: proto.Bool(true)}.Build()} +m2.OneofField = &pb2.M2_MsgOneof{pb2.M2_builder{B: proto.Bool(true)}.Build()} +m2.OneofField = &pb2.M2_MsgOneof{MsgOneof: pb2.M2_builder{}.Build()} +m2.OneofField = &pb2.M2_MsgOneof{pb2.M2_builder{}.Build()} +`, + want: map[Level]string{ + Green: ` +m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build()) +m2.SetMsgOneof(pb2.M2_builder{B: proto.Bool(true)}.Build()) +m2.SetMsgOneof(pb2.M2_builder{}.Build()) +m2.SetMsgOneof(pb2.M2_builder{}.Build()) +`, + }, + }, { + desc: "variable of wrapper type", + srcfiles: []string{"code.go"}, + in: ` +var scalarOneof *pb2.M2_StringOneof +_ = &pb2.M2{OneofField: scalarOneof} +`, + want: map[Level]string{ + Green: ` +var scalarOneof *pb2.M2_StringOneof +m2h2 := &pb2.M2{} +m2h2.OneofField = scalarOneof +_ = m2h2 +`, + Red: ` +var scalarOneof *pb2.M2_StringOneof +m2h2 := &pb2.M2{} +m2h2.SetStringOneof(scalarOneof.StringOneof) +_ = m2h2 +`, + }, + }, { + desc: "oneof field", + srcfiles: []string{"code.go"}, + in: ` +_ = &pb2.M2{OneofField: m2.OneofField} +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +m2h2.OneofField = m2.OneofField +_ = m2h2 +`, + Red: ` +m2h2 := &pb2.M2{} +// DO NOT SUBMIT: Migrate the direct oneof field access (go/go-opaque-special-cases/oneof.md). +m2h2.OneofField = m2.OneofField +_ = m2h2 +`, + }, + }} + runTableTests(t, tests) +} + +func TestMaybeUnsafeOneof(t *testing.T) { + tests := []test{{ + desc: "oneof message wrapper variable field", + srcfiles: []string{"code.go"}, + in: ` +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} +`, + want: map[Level]string{ + Green: ` +var msgOneof *pb2.M2_MsgOneof +m2h2 := &pb2.M2{} +m2h2.OneofField = msgOneof +_ = m2h2 +`, + Yellow: ` +var msgOneof *pb2.M2_MsgOneof +m2h2 := &pb2.M2{} +m2h2.OneofField = msgOneof +_ = m2h2 +`, + Red: ` +var msgOneof *pb2.M2_MsgOneof +m2h2 := &pb2.M2{} +m2h2.SetMsgOneof(protooneofdefault.ValueOrDefault(msgOneof.MsgOneof)) +_ = m2h2 +`, + }, + }, + + { + desc: "oneof message wrapper variable in builder", + srcfiles: []string{"code_test.go"}, + in: ` +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} +`, + want: map[Level]string{ + Green: ` +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} +`, + Yellow: ` +var msgOneof *pb2.M2_MsgOneof +_ = &pb2.M2{OneofField: msgOneof} +`, + Red: ` +var msgOneof *pb2.M2_MsgOneof +_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msgOneof.MsgOneof)}.Build() +`, + }, + }, + + { + desc: "oneof message field", + srcfiles: []string{"code.go"}, + in: ` +var msg *pb2.M2 +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}} +`, + want: map[Level]string{ + Green: ` +var msg *pb2.M2 +m2h2 := &pb2.M2{} +m2h2.OneofField = &pb2.M2_MsgOneof{msg} +_ = m2h2 +`, + Yellow: ` +var msg *pb2.M2 +m2h2 := &pb2.M2{} +m2h2.SetMsgOneof(protooneofdefault.ValueOrDefault(msg)) +_ = m2h2 +`, + }, + }, + + { + desc: "oneof message field in builder", + srcfiles: []string{"code_test.go"}, + in: ` +var msg *pb2.M2 +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}} +`, + want: map[Level]string{ + Green: ` +var msg *pb2.M2 +_ = &pb2.M2{OneofField: &pb2.M2_MsgOneof{msg}} +`, + Yellow: ` +var msg *pb2.M2 +_ = pb2.M2_builder{MsgOneof: protooneofdefault.ValueOrDefault(msg)}.Build() +`, + }, + }, + } + runTableTests(t, tests) +} + +func TestNestedSwitch(t *testing.T) { + tests := []test{ + { + desc: "nested switch", + extra: `func fmtErrorf(format string, a ...interface{}) { }`, + srcfiles: []string{"code_test.go"}, + in: ` +switch oneofField := m2.OneofField.(type) { +case *pb2.M2_BytesOneof: + _ = oneofField.BytesOneof +case *pb2.M2_MsgOneof: + switch nestedOneof := oneofField.MsgOneof.OneofField.(type) { + case *pb2.M2_StringOneof: + _ = nestedOneof.StringOneof + default: + fmtErrorf("%v", oneofField) + } +default: + fmtErrorf("%v", oneofField) +} +`, + want: map[Level]string{ + Red: ` +switch oneofField := m2.WhichOneofField(); oneofField { +case pb2.M2_BytesOneof_case: + _ = m2.GetBytesOneof() +case pb2.M2_MsgOneof_case: + switch m2.GetMsgOneof().WhichOneofField() { + case pb2.M2_StringOneof_case: + _ = m2.GetMsgOneof().GetStringOneof() + default: + fmtErrorf("%v", oneofField) + } +default: + fmtErrorf("%v", oneofField) +} +`, + }, + }, + } + runTableTests(t, tests) +}
diff --git a/internal/fix/oneofswitch.go b/internal/fix/oneofswitch.go new file mode 100644 index 0000000..471d51e --- /dev/null +++ b/internal/fix/oneofswitch.go
@@ -0,0 +1,576 @@ +// 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 fix + +import ( + "fmt" + "go/token" + "go/types" + "slices" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +// oneofSwitchPost handles type switches of protocol buffer oneofs. +func oneofSwitchPost(c *cursor) bool { + stmt, ok := c.Node().(*dst.TypeSwitchStmt) + if !ok { + c.Logf("ignoring %T (looking for TypeSwitchStmt)", c.Node()) + return true + } + initStmt := stmt.Init + + var typeAssert *dst.TypeAssertExpr + // oneofIdent is the variable introduced for the expression under the type + // assertion (e.g. "x" in "x := m.F.(type)"). + // + // We keep track of it so that we can replace it with accesses to m later on. + // + // This can be nil as "m.F.(type)" is also valid (type-switch without + // assignment), in which case we don't have to rewrite any references to the + // oneof in type-switch clauses. + var oneofIdent *dst.Ident + + switch a := stmt.Assign.(type) { + case *dst.AssignStmt: // "switch x := m.F.(type) {" + if len(a.Lhs) != 1 || len(a.Rhs) != 1 { + c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(a.Lhs), len(a.Rhs)) + return true + } + ident, ok := a.Lhs[0].(*dst.Ident) + if !ok { + c.Logf("ignoring Lhs=%T (looking for Ident)", a.Lhs[0]) + return true + } + oneofIdent = ident + typeAssert, ok = a.Rhs[0].(*dst.TypeAssertExpr) + if !ok { + c.Logf("ignoring Rhs=%T (looking for TypeAssertExpr)", a.Rhs[0]) + return true + } + case *dst.ExprStmt: // "switch m.F.(type) {" + typeAssert, ok = a.X.(*dst.TypeAssertExpr) + if !ok { + c.Logf("ignoring TypeSwitchStmt.Assign.X=%T (looking for TypeAssertExpr)", a.X) + return true + } + default: + c.Logf("ignoring TypeSwitchStmt.Assign=%T (looking for TypeAssertExpr or AssignStmt)", a) + return true + } + var oneofScope *types.Scope + if oneofIdent != nil { + ooi, ok := c.typesInfo.astMap[oneofIdent] + if !ok { + panic(fmt.Sprintf("failed to retrieve ast.Node for dst.Node: %v", oneofIdent)) + } + oneofScope = c.pkg.TypePkg.Scope().Innermost(ooi.Pos()) + } + + // Below we assume how the "Which" method is named based on either the name of + // the corresponding field or "Get" method. This isn't necessarily sound in + // the presence of naming conflicts but we don't want to repeat that + // complicated logic here. + var whichSig *types.Signature + var whichName string + var recv dst.Expr // "X" in "X.F.(type)" or "X.GetF().(type)" + + // We track oneof field and getter name here to keep the name-related logic + // in one place, at the top. We use those to rewrite oneof field access + // through field/getter later on. Ideally, we would use object identity + // instead of comparison by name, but we can't get that without messing with + // names, so we might as well got for name comaprisons. + var oneofFieldName string // "F" in "X.F.(type)" or "X.GetF().(type)" + var oneofGetName string // "GetF" corresponding to oneofFieldName + + // Find the WhichF() method that should replace "m.F.(type)" in + // switch m.F.(type) { + // => + // switch m.WhichF() { + if field, ok := c.trackedProtoFieldSelector(typeAssert.X); ok { + if !isOneof(c.typeOf(field)) { + c.Logf("ignoring field %v because it is not a oneof", field) + return true + } + oneofFieldName = field.Sel.Name + oneofGetName = "Get" + field.Sel.Name + whichName = "Which" + oneofFieldName + c.Logf("rewriting TypeAssertExpr on oneof field %s to %s CallExpr", oneofFieldName, whichName) + whichSig = whichOneofSignature(c.typeOf(field.X), whichName) + recv = field.X + } + + // Find the WhichF() method that should replace "m.GetF().(type)" in + // switch m.GetF().(type) { + // => + // switch m.WhichF() { + if call, ok := typeAssert.X.(*dst.CallExpr); ok { + sel, ok := call.Fun.(*dst.SelectorExpr) + if !ok || !isOneof(c.typeOf(call)) || !strings.HasPrefix(sel.Sel.Name, "Get") || !c.shouldUpdateType(c.typeOf(sel.X)) { + // Even if this is a call returning a oneof, we can't do anything about + // it. We don't add DO_NOT_SUBMIT tag because it's too easy to + // accidentally add it to unrelated type switches here. + c.Logf("ignoring: TypeAssertExpr recondition is not met") + return true + } + recv = sel.X + oneofGetName = sel.Sel.Name + oneofFieldName = oneofGetName[len("Get"):] + whichName = "Which" + oneofFieldName + c.Logf("rewriting TypeAssertExpr on oneof field %s to %s CallExpr", oneofFieldName, whichName) + whichSig = whichOneofSignature(c.typeOf(sel.X), whichName) + } + + // Not a oneof access (call or field) or we couldn't find the "Which" method. + if whichSig == nil { + c.Logf("ignoring: cannot find Which${Field} method") + return true + } + + // If recv may have side effects, then move it to the init statement if + // possible (bail otherwise) in order to avoid cloning side effects into + // the body of the type-switch. + if !c.isSideEffectFree(recv) { + if initStmt != nil { + if c.lvl.ge(Red) { + c.numUnsafeRewritesByReason[IncompleteRewrite]++ + markMissingRewrite(stmt, "type switch with side effects and init statement") + } + c.Logf("ignoring: cannot move init statement with side effects") + return true + } + newVar := dst.NewIdent("xmsg") + c.setType(newVar, c.typeOf(recv)) + initStmt = &dst.AssignStmt{ + Lhs: []dst.Expr{newVar}, + Tok: token.DEFINE, + Rhs: []dst.Expr{recv}, + } + recv = cloneIdent(c, newVar) + } + + // Construct the "recv.WhichOneofField()" method call. + whichMethod := &dst.SelectorExpr{ + X: recv, + Sel: dst.NewIdent(whichName), + } + c.setType(whichMethod, whichSig) + c.setType(whichMethod.Sel, whichSig) + whichCall := &dst.CallExpr{Fun: whichMethod} + c.setType(whichCall, whichSig.Results().At(0).Type()) + + // First, verify that we can do necessary rewrites to the entire type switch + // statement. + // + // In "switch x := m.F.(type) {" and similar, we replace all references + // to "x" (the oneof object, "oneofIdent") with method calls on "m". + definedVarUsedInDefaultCase := false + if oneofIdent != nil { + var maybeUnsafe bool + if !c.lvl.ge(Red) { + // Unfortunately "x" in "x := X.(type)" has no identity, so we must + // rely on name matching to replace all instances of "x" with + // something else. We want to be careful: if any clause refers to + // more than one "x" ("x" has identity in non-default clauses) that + // could be a oneof then we don't do anything. + // + // If any non-default clause refers to "x" (that could be a oneof) + // not in the context of a selector (i.e. it's not "x.F") then we + // also don't do anything. Oneofs are not a "thing" and can't be + // referenced like this in the new API. + // + // The sole exception is printf-like calls in the default clause + // (see below), where we replace the oneof reference with a "Which" + // method call. + for _, clause := range stmt.Body.List { + var clauseIdent *dst.Ident + dstutil.Apply(clause, func(cur *dstutil.Cursor) bool { + ident, ok := cur.Node().(*dst.Ident) + if !ok || ident.Name != oneofIdent.Name { + return true + } + isDefaultClause := clause.(*dst.CaseClause).List == nil // Ident has no type in "default". + if !isDefaultClause && !isOneofWrapper(c, ident) { + return true + } + // Usually we don't allow references to the "oneof" itself, but + // we make an exception for print-like statements as + // it's comment to say: `fmt.Errorf("unknown case %v", oneofField)`. + if c.looksLikePrintf(cur.Parent()) { + return true + } + if _, ok := cur.Parent().(*dst.SelectorExpr); !ok { + maybeUnsafe = true + return false + } + if clauseIdent == nil { + clauseIdent = ident + return true + } + if c.objectOf(clauseIdent) == c.objectOf(ident) { + return true + } + maybeUnsafe = true + return false + }, nil) + if maybeUnsafe { + c.Logf("ignoring: cannot rewrite oneof usage safely") + return true + } + } + } + + // Then do the necessary rewrites. + for _, clause := range stmt.Body.List { + dstutil.Apply(clause, func(cur *dstutil.Cursor) bool { + n := cur.Node() + // First, handle direct references to the oneof itself, + // in default clauses: + // fmt.Errorf("Unknown case %v", oneofField) + // => + // fmt.Errorf("Unknown case %v", m.WhichOneofField()) + // + // This works well, because oneof cases have String() method. + // + // Note that we do the rewrite regardless of what's in the format + // string (we don't want to get into the business of parsing format + // strings now), so we risk: + // fmt.Errorf("Unknown case %T", m.WhichOneofField()) + // Which is not ideal, but not unreasonable. + if c.looksLikePrintf(cur.Parent()) { + // rewrite the formatting verb (if any) to %v: + // fmt.Errorf("%T", m2.OneofField) => fmt.Errorf("%v", m2.OneofField) + // The other part of the expression is rewritten below. + // This function does not handle escaped '%' character. + // Implementing a full formatting string parser is out of scope here. + rewriteVerb := func() { + call := cur.Parent().(*dst.CallExpr) + argIndex := slices.Index(call.Args, cur.Node().(dst.Expr)) + if argIndex == -1 { + panic(fmt.Sprintf("could not find argument %v in call expression %v. Did you call rewriteVerb after replacing the Node?", cur.Node(), call)) + } + for i, p := range call.Args { + slit, ok := p.(*dst.BasicLit) + if !ok { + continue + } + if slit.Kind != token.STRING { + continue + } + numFormattedArg := argIndex - i + idx := -1 + for i, r := range slit.Value { + if r == '%' { + numFormattedArg-- + if numFormattedArg == 0 { + idx = i + break + } + } + } + // There are not enough formatting verbs. + if idx == -1 { + return + } + if idx == len(slit.Value) || slit.Value[idx+1] == 'v' { + break + } + // No need to clone as a literal cannot be shared between Nodes. + slit.Value = slit.Value[:idx] + "%v" + slit.Value[idx+2:] + } + } + // switch m := f(); oneofField := m.OneofField.(type) {... + // default: + // return fmt.Errorf("Unknown case %v", oneofField) + // => + // switch m := f(); oneofField := m.OneofField.(type) {... + // default: + // return fmt.Errorf("Unknown case %v", m.WhichOneofField()) + if ident, ok := n.(*dst.Ident); ok && ident.Name == oneofIdent.Name { + rewriteVerb() + definedVarUsedInDefaultCase = true + if initStmt == nil { + return true + } + c.Logf("rewriting usage of %q", oneofIdent.Name) + if maybeUnsafe { + c.numUnsafeRewritesByReason[MaybeSemanticChange]++ + } + cur.Replace(cloneSelectorCallExpr(c, whichCall)) + return true + } + // fmt.Errorf("Unknown case %v", m.OneofField) + // => + // fmt.Errorf("Unknown case %v", m.WhichOneofField()) + if field, ok := c.trackedProtoFieldSelector(n); ok && isOneof(c.typeOf(field)) && field.Sel.Name == oneofFieldName { + c.Logf("rewriting usage of %q", oneofIdent.Name) + if maybeUnsafe { + c.numUnsafeRewritesByReason[MaybeSemanticChange]++ + } + rewriteVerb() + cur.Replace(cloneSelectorCallExpr(c, whichCall)) + return true + } + // fmt.Errorf("Unknown case %v", m.GetOneofField()) + // => + // fmt.Errorf("Unknown case %v", m.WhichOneofField()) + if call, ok := n.(*dst.CallExpr); ok && isOneof(c.typeOf(call)) { + if sel, ok := call.Fun.(*dst.SelectorExpr); ok && sel.Sel.Name == oneofGetName { + c.Logf("rewriting usage of %q", oneofIdent.Name) + if maybeUnsafe { + c.numUnsafeRewritesByReason[MaybeSemanticChange]++ + } + rewriteVerb() + cur.Replace(cloneSelectorCallExpr(c, whichCall)) + return true + } + } + } + + // Handle "oneofField.F" => "recv.F" + // Subsequent passes may then do more rewrites. For example: + // "recv.F" => "recv.{Get,Set,Has,Clear}F()" + sel, ok := n.(*dst.SelectorExpr) + if !ok { + c.Logf("ignoring usage %v of type %T (looking for SelectorExpr)", n, n) + return true + } + ident, ok := sel.X.(*dst.Ident) + if !ok { + c.Logf("ignoring usage %v of type SelectorExpr.X.%T (looking for SelectorExpr.X.Ident)", sel, sel.X) + return true + } + // We cannot do an object based comparison here + // because variable declared in the initializer + // of a switch don't have an associated object + // (or there is a bug in our tooling that removes + // this association). + if ident.Name != oneofIdent.Name { + c.Logf("ignoring %v because it is not using %v", sel, oneofIdent) + return true + } + isDefaultClause := clause.(*dst.CaseClause).List == nil + if !isDefaultClause && !isOneofWrapper(c, ident) { + c.Logf("ignoring usage %v: not in default clause and not a oneof wrapper", sel) + return true + } + + // Check if the name is shadwed in a nested block: + // + // switch oneofField := m2.OneofField.(type) { + // case *pb.M2_StringOneof: + // { + // oneofField := &O{} + // _ = oneofField.StringOneof + // } + // } + // + // For switch-case statements there is one scope for the switch + // which has child scopes, one for each case clause. A variable + // defined in the switch condition (oneofField in the example) + // is not part of the parent (switch) scope but is defined in + // every child (case) scope. + // This means to check if an identifier references the variable + // defined by the switch condition, we must check if the + // referenced object is any of the child (case) scopes. + if oneofScope == nil { + c.Logf("ignoring usage %v: no scope for oneof found", n) + return true + } + astNode, ok := c.typesInfo.astMap[ident] + if !ok { + panic(fmt.Sprintf("failed to retrieve ast.Node for dst.Node: %p", ident)) + } + scope := c.pkg.TypePkg.Scope().Innermost(astNode.Pos()) + s, _ := scope.LookupParent(ident.Name, astNode.Pos()) + if s == nil { + panic(fmt.Sprintf("invalid package: cannot resolve %v", astNode)) + } + found := false + for i := 0; i < oneofScope.NumChildren(); i++ { + if oneofScope.Child(i) == s { + found = true + break + } + } + // The variable defined in the switch is shadowed by another + // definition. + if !found { + c.Logf("ignoring usage %v: does not reference oneof type switch variable", astNode) + return true + } + + c.Logf("rewriting usage %v", recv) + switch recv := recv.(type) { + case *dst.Ident: + sel.X = cloneIdent(c, recv) + case *dst.IndexExpr: + sel.X = cloneIndexExpr(c, recv) + case *dst.SelectorExpr: + sel.X = cloneSelectorExpr(c, recv) + case *dst.CallExpr: + sel.X = cloneSelectorCallExpr(c, recv) + default: + panic(fmt.Sprintf("unsupported receiver AST type %T in oneof type switch", recv)) + } + if maybeUnsafe { + c.numUnsafeRewritesByReason[MaybeSemanticChange]++ + } + return true + }, nil) + } + } + + // Rewrite clauses: + // case *pb.M_Oneof: + // => + // case pb.M_Oneof_case: + for _, clause := range stmt.Body.List { + cl, ok := clause.(*dst.CaseClause) + if !ok { + continue + } + for i, typ := range cl.List { + if ident, ok := typ.(*dst.Ident); ok && ident.Name == "nil" { + zero := &dst.BasicLit{Kind: token.INT, Value: "0"} + c.setType(zero, types.Typ[types.Int32]) + c.Logf("rewriting nil-case to 0-case in %v", cl) + cl.List[i] = zero + continue + } + + se, ok := typ.(*dst.StarExpr) + if !ok { + c.Logf("skipping case %v of type %T (looking for StarExpr)", cl, typ) + continue + } + sel, ok := se.X.(*dst.SelectorExpr) + if !ok { + c.Logf("skipping case %v of type %T (looking for SelectorExpr)", cl, se) + continue + } + + // Split the oneof wrapper type name into its parts: + parts := strings.Split(strings.TrimRight(sel.Sel.Name, "_"), "_") + // parts[0] is the parent of the oneof field + // parts[len(parts)-1] is the oneof field + if len(parts) < 2 { + c.Logf("skipping case %v, sel %q does not split into parent_field", cl, sel.Sel.Name) + continue + } + field := parts[len(parts)-1] + suffix := "_case" + // There are two cases in which the wrapper type name ends in an + // underscore: + // + // 1. The field name itself conflicts. This means the field was + // called reset, marshal, etc., conflicting with the proto + // generated code method names. In this case, the _case constant + // will use two underscores. + // + // 2. The field name conflicts with another name in the .proto file. + // In this case, the _case constant will not use two underscores. + if strings.HasSuffix(sel.Sel.Name, "_") && !conflictingNames[field] { + suffix = "case" + } + + sel.Sel.Name += suffix + sel.Decs.Before = se.Decs.Before + sel.Decs.Start = append(sel.Decs.Start, se.Decs.Start...) + sel.Decs.End = append(sel.Decs.End, se.Decs.End...) + sel.Decs.After = se.Decs.After + c.Logf("rewriting case %+#v", cl.List[i]) + cl.List[i] = sel + } + } + + c.Logf("rewriting SwitchStmt") + + // The TypeSwitchStmt initializes a local variable that we need to keep + // and there is no init statement. + // switch oneofField := m.OneofField.(type) {... + // default: + // return fmt.Errorf("Unknown case %v", oneofField) + // => + // switch oneofField := m.WhichOneof() {... + // default: + // return fmt.Errorf("Unknown case %v", oneofField) + // If it were not used we would rewrite it to: + // switch oneofField := m.OneofField.(type) { + // ... + // default: + // return fmt.Errorf("Unknown case") + // => + // switch m.WhichOneof() {... + // ... + // default: + // return fmt.Errorf("Unknown case") + var tag dst.Expr = cloneSelectorCallExpr(c, whichCall) + if definedVarUsedInDefaultCase && initStmt == nil { + if oneofIdent == nil { + panic("identNil") + } + c.setType(oneofIdent, types.Typ[types.Invalid]) + initStmt = &dst.AssignStmt{ + Lhs: []dst.Expr{ + cloneIdent(c, oneofIdent), + }, + Rhs: []dst.Expr{tag}, + Tok: token.DEFINE, + } + tag = cloneIdent(c, oneofIdent) + c.setType(tag, c.typeOf(oneofIdent)) + } + // Change the type from TypeSwitchStmt to SwitchStmt. + c.Replace(&dst.SwitchStmt{ + Init: initStmt, + Tag: tag, + Body: stmt.Body, + Decs: dst.SwitchStmtDecorations{ + NodeDecs: stmt.Decs.NodeDecs, + Switch: stmt.Decs.Switch, + Init: stmt.Decs.Init, + Tag: stmt.Decs.Assign, + }, + }) + + return true +} + +// whichOneofSignature returns the signature type of the method, of type t, with +// the given name. It must have "Which" prefix (i.e. it is the method for +// getting the oneof type). It returns an in-band nil (instead of a separate +// "ok" bool) if such method does not exist in order to simplify the caller code +// above. +func whichOneofSignature(t types.Type, name string) *types.Signature { + if !strings.HasPrefix(name, "Which") { + panic(fmt.Sprintf("whichOneofSignature called with %q; must have 'Which' prefix", name)) + } + ptr, ok := t.(*types.Pointer) + if !ok { + return nil + } + typ, ok := ptr.Elem().(*types.Named) + if !ok { + return nil + } + var m *types.Func + for i := 0; i < typ.NumMethods(); i++ { + m = typ.Method(i) + if m.Name() == name { + break + } + } + if m == nil { + return nil + } + sig := m.Type().(*types.Signature) + if sig.Results().Len() != 1 { + return nil + } + return sig +}
diff --git a/internal/fix/outputparam.go b/internal/fix/outputparam.go new file mode 100644 index 0000000..16af42d --- /dev/null +++ b/internal/fix/outputparam.go
@@ -0,0 +1,232 @@ +// 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 fix + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/dave/dst" + "google.golang.org/open2opaque/internal/protodetecttypes" +) + +// Recognize the *resp = <message> pattern and rewrite it to proto.Merge. +func outputParamPre(c *cursor) bool { + as, ok := c.Node().(*dst.AssignStmt) + if !ok { + return true + } + + if len(as.Lhs) != 1 || len(as.Rhs) != 1 { + c.Logf("ignoring AssignStmt with len(lhs)=%d, len(rhs)=%d (looking for 1, 1)", len(as.Lhs), len(as.Rhs)) + return true + } + + switch as.Rhs[0].(type) { + case *dst.Ident: // variable assignment + case *dst.CompositeLit: // struct literal + case *dst.CallExpr: // builder + default: + c.Logf("ignoring AssignStmt with rhs %T, want Ident, CompositeLit or CallExpr", as.Rhs[0]) + return true + } + + se, ok := as.Lhs[0].(*dst.StarExpr) + if !ok { + c.Logf("ignoring AssignStmt with lhs %T, want StarExpr", as.Lhs[0]) + return true + } + if _, ok := se.X.(*dst.Ident); !ok { + c.Logf("ignoring AssignStmt with lhs.X %T, want Ident", se.X) + return true + } + + T := c.typeOf(se) + if !(protodetecttypes.Type{T: T}).IsMessage() { + c.Logf("ignoring AssignStmt for non-proto message (%v)", T) + return true + } + + if resetNeeded(c, as, se.X.(*dst.Ident)) { + resetFun := &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(protoImport)}, + Sel: &dst.Ident{Name: "Reset"}, + } + resetCall := &dst.CallExpr{ + Fun: resetFun, + Args: []dst.Expr{ + cloneIdent(c, se.X.(*dst.Ident)), + }, + Decs: dst.CallExprDecorations{ + NodeDecs: dst.NodeDecs{ + Before: as.Decs.NodeDecs.Before, + Start: as.Decs.NodeDecs.Start, + }, + }, + } + as.Decs.NodeDecs.Before = dst.None + as.Decs.NodeDecs.Start = nil + c.setType(resetCall, types.Typ[types.Invalid]) + c.setType(resetFun, types.Typ[types.Invalid]) + c.setType(resetFun.X, types.Typ[types.Invalid]) + c.setType(resetFun.Sel, types.Typ[types.Invalid]) + c.InsertBefore(&dst.ExprStmt{X: resetCall}) + } + + // Replace the AssignStmt with a call to proto.Merge + mergeFun := &dst.SelectorExpr{ + X: &dst.Ident{Name: c.imports.name(protoImport)}, + Sel: &dst.Ident{Name: "Merge"}, + } + unaryRHS := &dst.UnaryExpr{ + Op: token.AND, + X: as.Rhs[0], + } + mergeCall := &dst.CallExpr{ + Fun: mergeFun, + Args: []dst.Expr{ + se.X, + unaryRHS, + }, + Decs: dst.CallExprDecorations{ + NodeDecs: as.Decs.NodeDecs, + }, + } + c.setType(mergeCall, types.Typ[types.Invalid]) + c.setType(mergeFun, types.Typ[types.Invalid]) + c.setType(mergeFun.X, types.Typ[types.Invalid]) + c.setType(mergeFun.Sel, types.Typ[types.Invalid]) + c.setType(unaryRHS, types.NewPointer(T)) + stmt := &dst.ExprStmt{X: mergeCall} + c.Replace(stmt) + updateASTMap(c, as, stmt) + + return true +} + +func isStubbyHandler(c *cursor, ft *ast.FuncType) bool { + if ft.Results == nil || len(ft.Results.List) != 1 { + c.Logf("not a stubby handler: not precisely one return value") + return false + } + if id, ok := ft.Results.List[0].Type.(*ast.Ident); !ok || id.Name != "error" { + c.Logf("not a stubby handler: not returning an error") + return false + } + if got, want := len(ft.Params.List), 3; got != want { + c.Logf("incorrect number of parameters for a stubby handler: got %d, want %d", got, want) + return false + } + params := ft.Params.List + + se, ok := params[0].Type.(*ast.SelectorExpr) + if !ok { + c.Logf("first parameter is not context.Context") + return false + } + if id, ok := se.X.(*ast.Ident); !ok || id.Name != "context" { + c.Logf("first parameter is not context.Context") + return false + } + if se.Sel.Name != "Context" { + c.Logf("first parameter is not context.Context") + return false + } + + // Both remaining parameters must be proto messages + for _, param := range params[1:] { + dstT, ok := c.typesInfo.dstMap[param.Type] + if !ok { + c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", param.Type, param.Type) + return false + } + + T := c.typeOf(dstT.(dst.Expr)) + ptr, ok := T.(*types.Pointer) + if !ok { + c.Logf("parameter %T is not a proto message", param.Type) + return false + } + T = ptr.Elem() + if !(protodetecttypes.Type{T: T}).IsMessage() { + c.Logf("parameter %T is not a proto message", param.Type) + return false + } + } + + return true +} + +// resetNeeded figures out if for the specified AssignStmt, a proto.Reset() call +// needs to be inserted before or not. +// +// resetNeeded looks at the AST of the current scope, considering all statements +// between the scope opener (function declaration) and the AssignStmt: +// +// func (*Srv) Handler(ctx context.Context, req *pb.Req, resp *pb.Resp) error { +// … // checked by resetNeeded() +// mm2.I32 = 23 // uses mm2, not resp +// *resp = mm2 // AssignStmt +// } +func resetNeeded(c *cursor, as *dst.AssignStmt, id *dst.Ident) bool { + innerMost, opener := c.enclosingASTStmt(as) + if innerMost == nil { + return true + } + enclosing, ok := c.typesInfo.dstMap[innerMost] + if !ok { + c.Logf("BUG: no corresponding dave/dst node for go/ast node %T / %+v", innerMost, innerMost) + return true + } + + var ft *ast.FuncType + switch x := opener.(type) { + case *ast.FuncDecl: + ft = x.Type + case *ast.FuncLit: + ft = x.Type + default: + c.Logf("scope opener is %T, not a FuncDecl or FuncLit", opener) + return true + } + + if !isStubbyHandler(c, ft) { + c.Logf("scope opener is not a stubby handler") + return true + } + + lastSeen := false + usageFound := false + var visit visitorFunc + xObj := c.objectOf(id) + + visit = func(n dst.Node) dst.Visitor { + if n == as { + lastSeen = true + return nil + } + if lastSeen { + return nil + } + + if id, ok := n.(*dst.Ident); ok { + // Is the variable ever used? + if usesObject(c, id, xObj) { + usageFound = true + return nil + } + } + + return visit // recurse into children + } + dst.Walk(visit, enclosing) + // proto.Reset() calls are definitely needed when: + // + // 1. !lastSeen — we couldn’t find the usage of <sel> (bug?) + // + // 2. or usageFound — we did find a usage that we didn’t expect. + return !lastSeen || usageFound +}
diff --git a/internal/fix/rules.go b/internal/fix/rules.go new file mode 100644 index 0000000..85a5fcf --- /dev/null +++ b/internal/fix/rules.go
@@ -0,0 +1,411 @@ +// 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 fix + +import ( + "fmt" + "go/token" + "go/types" + "reflect" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" + "golang.org/x/exp/slices" +) + +func init() { + rewrites = []rewrite{ + // outputparam.go + {name: "outputParamPre", pre: outputParamPre}, + // usepointers.go + {name: "usePointersPre", pre: usePointersPre}, + // incdec.go + {name: "incDecPre", pre: incDecPre}, + // The hasPre stage needs to run before convertToSetterPost because it + // generates direct fields accesses on the lhs of assignments which + // convertToSetterPost rewrites to setters. + // + // has.go + {name: "hasPre", pre: hasPre}, + // converttosetter.go + {name: "convertToSetterPost", post: convertToSetterPost}, + // oneofswitch.go + {name: "oneofSwitchPost", pre: oneofSwitchPost}, + // appendprotos.go + {name: "appendProtosPre", pre: appendProtosPre}, + // The assignSwapPre stage needs to run before assignPre and getPost + // because it untangles swap assignments into two assignments, which + // will afterwards be rewritten into getters (getPost) and setters + // (assignPre). + // + // assignswap.go + {name: "assignSwapPre", pre: assignSwapPre}, + // get.go + {name: "getPre", pre: getPre}, + {name: "getPost", post: getPost}, + // assign.go + {name: "assignPre", pre: assignPre}, + {name: "assignOpPre", pre: assignOpPre}, + {name: "assignPost", post: assignPost}, + // build.go + {name: "buildPost", post: buildPost}, + } +} + +const protoImport = "google.golang.org/protobuf/proto" + +func markMissingRewrite(n dst.Node, what string) { + marker := fmt.Sprintf("/* DO_NOT_SUBMIT: missing rewrite for %s */", what) + decs := n.Decorations() + for _, d := range decs.End { + if d == marker { + return + } + } + decs.End = append([]string{marker}, decs.End...) +} + +// visitorFunc is a convenience type to use a function literal as a dst.Visitor. +type visitorFunc func(n dst.Node) dst.Visitor + +// Visit implements dst.Visitor. +func (v visitorFunc) Visit(n dst.Node) dst.Visitor { + return v(n) +} + +var ( + dstExprType = reflect.TypeOf((*dst.Expr)(nil)).Elem() + dstIdentType = reflect.TypeOf((*dst.Ident)(nil)) +) + +func isStatementOrDeclaration(n dst.Node) bool { + switch n.(type) { + case dst.Stmt: + return true + case dst.Decl: + return true + default: + return false + } +} + +// addCommentAbove locates the specified expression (can be part of a line), +// walks up to the parent nodes until it encounters a statement or declaration +// (full line) and adds the specified comment. +func addCommentAbove(root dst.Node, expr dst.Expr, comment string) { + // Pre-allocate to avoid memory allocations up until 100 levels of nesting. + stack := make([]dst.Node, 0, 100) + + dstutil.Apply(root, + func(cur *dstutil.Cursor) bool { + verdict := true // keep recursing + if cur.Node() == expr { + // Insert comment above the closest dst.Stmt or dst.Decl. + for i := len(stack) - 1; i >= 0; i-- { + if !isStatementOrDeclaration(stack[i]) { + continue + } + decs := stack[i].Decorations() + if slices.Contains(decs.Start, comment) { + // This statement contains multiple expressions, but the + // comment was already added. Skip so that we do not add + // the same comment multiple times. + break + } + decs.Start = append(decs.Start, comment) + decs.Before = dst.NewLine + break + } + verdict = false // stop recursing + } + stack = append(stack, cur.Node()) // push + return verdict + }, + func(cur *dstutil.Cursor) bool { + stack = stack[:len(stack)-1] // pop + return true + }) +} + +// scalarTypeZeroExpr returns an expression for zero value of the given type +// which must be a protocol buffer scalar type. +func scalarTypeZeroExpr(c *cursor, t types.Type) dst.Expr { + out := &dst.Ident{} + c.setType(out, t) + + if isBytes(t) { + out.Name = "nil" + return out + } + + if isEnum(t) { + out.Name = "0" + return out + } + + bt, ok := t.(*types.Basic) + if !ok { + panic(fmt.Sprintf("scalarTypeZeroExpr called with %T", t)) + } + basicLit := &dst.BasicLit{} + c.setType(basicLit, bt) + switch bt.Kind() { + case types.UntypedBool, types.Bool: + out.Name = "false" + return out + case types.UntypedInt, types.Int, types.Int32, types.Int64, types.Uint32, types.Uint64: + basicLit.Kind = token.INT + basicLit.Value = "0" + case types.UntypedFloat, types.Float32, types.Float64: + basicLit.Kind = token.FLOAT + basicLit.Value = "0.0" + case types.UntypedString, types.String: + basicLit.Kind = token.STRING + basicLit.Value = `""` + default: + panic(fmt.Sprintf("unrecognized kind %d of type %s", bt.Kind(), t)) + } + return basicLit +} + +func basicTypeHelperName(t *types.Basic) string { + switch t.Kind() { + case types.Bool: + return "Bool" + case types.Int: + return "Int" + case types.Int32: + return "Int32" + case types.Int64: + return "Int64" + case types.Uint32: + return "Uint32" + case types.Uint64: + return "Uint64" + case types.Float32: + return "Float32" + case types.Float64: + return "Float64" + case types.String: + return "String" + case types.UntypedBool: + return "Bool" + case types.UntypedInt: + return "Int" + case types.UntypedFloat: + return "Float" + case types.UntypedString: + return "String" + default: + panic(fmt.Sprintf("unrecognized kind %d of type %s", t.Kind(), t)) + } +} + +func addr(c *cursor, expr dst.Expr) dst.Expr { + if e, ok := expr.(*dst.StarExpr); ok { + return e.X + } + out := &dst.UnaryExpr{ + Op: token.AND, + X: expr, + } + updateASTMap(c, expr, out) + c.setType(out, types.NewPointer(c.typeOf(expr))) + return out +} + +func isAddr(expr dst.Node) bool { + ue, ok := expr.(*dst.UnaryExpr) + return ok && ue.Op == token.AND +} + +// expr2stmt wraps an expression as a statement +func (c *cursor) expr2stmt(expr dst.Expr, src dst.Node) *dst.ExprStmt { + stmt := &dst.ExprStmt{X: expr} + updateASTMap(c, src, stmt) + return stmt +} + +func isPtr(t types.Type) bool { + _, ok := t.Underlying().(*types.Pointer) + return ok +} + +func isBasic(t types.Type) bool { + _, ok := t.(*types.Basic) + return ok +} + +func isBytes(t types.Type) bool { + s, ok := t.(*types.Slice) + if !ok { + return false + } + elem, ok := s.Elem().(*types.Basic) + return ok && elem.Kind() == types.Byte +} + +func isEnum(t types.Type) bool { + n, ok := t.(*types.Named) + if !ok { + return false + } + _, ok = n.Underlying().(*types.Basic) + return ok +} + +func isScalar(t types.Type) bool { + return isBasic(t) || isEnum(t) || isBytes(t) +} + +func isMsg(t types.Type) bool { + p, ok := t.Underlying().(*types.Pointer) + if !ok { + return false + } + _, ok = p.Elem().Underlying().(*types.Struct) + return ok +} + +func isOneof(t types.Type) bool { + _, ok := t.Underlying().(*types.Interface) + return ok +} + +func isOneofWrapper(c *cursor, x dst.Expr) bool { + t := c.underlyingTypeOf(x) + if p, ok := t.(*types.Pointer); ok { + t = p.Elem().Underlying() + } + s, ok := t.(*types.Struct) + if !ok || s.NumFields() != 1 { + return false + } + for _, tag := range strings.Split(reflect.StructTag(s.Tag(0)).Get("protobuf"), ",") { + if tag == "oneof" { + return true + } + } + return false +} + +func isPtrToBasic(t types.Type) bool { + p, ok := t.Underlying().(*types.Pointer) + if !ok { + return false + } + _, ok = p.Elem().Underlying().(*types.Basic) + return ok +} + +// sel2call converts a selector expression to a call expression (a direct field access to a method +// call). For example, "m.F = v" is replaced with "m.SetF(v)" where "m.F" is the selector +// expression, "v" is the val, and "Set" is the prefix. +// +// If val is nil, it is not added as an argument to the call expression. Prefix can be one of "Get", +// "Set", "Clear", "Has", +// +// This function does the necessary changes to field names to resolve conflicts. +func sel2call(c *cursor, prefix string, sel *dst.SelectorExpr, val dst.Expr, decs dst.NodeDecs) *dst.CallExpr { + name := fixConflictingNames(c.typeOf(sel.X), prefix, sel.Sel.Name) + fnsel := &dst.Ident{ + Name: prefix + name, + } + fn := &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: sel.X, + Sel: fnsel, + }, + } + fn.Decs.NodeDecs = decs + if val != nil { + val = dstutil.Unparen(val) + fn.Args = []dst.Expr{val} + } + + t := c.underlyingTypeOf(sel.Sel) + if isPtrToBasic(t) { + t = t.(*types.Pointer).Elem() + } + var pkg *types.Package + if use := c.objectOf(sel.Sel); use != nil { + pkg = use.Pkg() + } + value := types.NewParam(token.NoPos, pkg, "_", t) + recv := types.NewParam(token.NoPos, pkg, "_", c.underlyingTypeOf(sel.X)) + switch prefix { + case "Get": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(value), false)) + c.setType(fn, t) + case "Set": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(value), types.NewTuple(), false)) + c.setVoidType(fn) + case "Clear": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(), false)) + c.setVoidType(fn) + case "Has": + c.setType(fnsel, types.NewSignature(recv, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, pkg, "_", types.Typ[types.Bool])), false)) + c.setType(fn, types.Typ[types.Bool]) + default: + panic("bad function name prefix '" + prefix + "'") + } + c.setType(fn.Fun, c.typeOf(fnsel)) + return fn +} + +// NewSrc creates a test Go package for test examples. +// +// Tests can access: +// +// a fake version of the proto package, "proto" +// a fake generated profile package, "pb" +// a fake proto2 object, "m2" +// a fake proto3 object, "m3" +// +// Note that there's only one instance of "m2" and "m3". We may need more +// instances when the analysis is smart enough to do different rewrites for +// operations on two different objects than on a single one. Currently, for +// example: +// +// m2.S = m2.S +// +// is not recognized as having the same object on both sides. Hence we consider +// it as losing aliasing and clear semantics when rewritten as: +// +// m2.SetS(m2.GetS()) +// +// newSrc doesn't introduce new access patterns recognized by the migration tool +// so that tests can rely on all returned accesses coming from code added in +// those tests. +func NewSrc(in, extra string) string { + return `// 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 p + +import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto" +import pb3 "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto" +import proto "google.golang.org/protobuf/proto" +import "unsafe" +import "context" + +var _ unsafe.Pointer +var _ = proto.String +var _ = context.Background + +func test_function() { + m2 := new(pb2.M2) + m2a := new(pb2.M2) + _, _ = m2, m2a + m3 := new(pb3.M3) + _ = m3 + _ = "TEST CODE STARTS HERE" +` + in + ` + _ = "TEST CODE ENDS HERE" +} +` + extra +}
diff --git a/internal/fix/rules_common_test.go b/internal/fix/rules_common_test.go new file mode 100644 index 0000000..53f88df --- /dev/null +++ b/internal/fix/rules_common_test.go
@@ -0,0 +1,354 @@ +// 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 fix + +import ( + "context" + "fmt" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/kylelemons/godebug/diff" + spb "google.golang.org/open2opaque/internal/dashboard" + "google.golang.org/open2opaque/internal/o2o/fakeloader" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/o2o/syncset" +) + +const dumpSrcOnFail = false + +type fakeLoaderBase struct { + ImportPathToFiles map[string][]string + PathToContent map[string]string + ExportFor fakeloader.ExportForFunc +} + +// flBase contains the fakeloader base values, i.e. the parts of the test which +// do not change between each test (testdata protobuf packages and Go Protobuf +// itself). +var flBase = func() *fakeLoaderBase { + + flb, err := goListBase() + if err != nil { + panic(err) + } + return flb +}() + +func fixSource(ctx context.Context, src, srcfile string, cPkgSettings ConfiguredPackage, levels []Level) (lvl2src map[Level]string, lvl2stats map[Level][]*spb.Entry, err error) { + // Wrap the single source file in a package that can be loaded. Also add fake support + // packages (the proto library and compiled .proto file with messages). + ruleName := "google.golang.org/open2opaque/internal/fix/testdata/fake" + prefix := ruleName + "/" + + srcfile = prefix + srcfile + + // Add/overwrite the source file: + flBase.ImportPathToFiles[ruleName] = []string{srcfile} + flBase.PathToContent[srcfile] = src + + l := fakeloader.NewFakeLoader( + flBase.ImportPathToFiles, + flBase.PathToContent, + nil, + flBase.ExportFor) + pkg, err := loader.LoadOne(ctx, l, &loader.Target{ID: ruleName}) + if err != nil { + return nil, nil, fmt.Errorf("can't fix source: %v", err) + } + + cPkg := ConfiguredPackage{ + Loader: l, + Pkg: pkg, + TypesToUpdate: cPkgSettings.TypesToUpdate, + BuilderTypes: cPkgSettings.BuilderTypes, + Levels: levels, + ProcessedFiles: syncset.New(), + UseBuilders: BuildersTestsOnly, + } + fixed, err := cPkg.Fix() + if err != nil { + return nil, nil, fmt.Errorf("can't fix source: %v", err) + } + var lvls []Level + for lvl := range fixed { + lvls = append(lvls, lvl) + } + sort.Slice(lvls, func(i, j int) bool { + return !lvls[i].ge(lvls[j]) + }) + want := append([]Level{None}, levels...) + if d := cmp.Diff(want, lvls); d != "" { + return nil, nil, fmt.Errorf("Package() = %v;\nwant map with keys %v:\n%s\n", lvls, want, d) + } + for lvl, fs := range fixed { + if len(fs) != 1 { + return nil, nil, fmt.Errorf("Package(1 source) = %s/%d; want 1 for each None,Green,Yellow,Red", lvl, len(fs)) + } + } + + // Extract code that the test cares about. It is between + // _ = "TEST CODE STARTS HERE" + // and + // _ = "TEST CODE ENDS HERE" + // Everything else wraps that code in a package that can be loaded. + // Note that wrapped code can have different indentation levels than what the test + // expects. Fix that too. + const start = `_ = "TEST CODE STARTS HERE"` + lvl2src = map[Level]string{} + lvl2stats = map[Level][]*spb.Entry{} + for lvl, srcs := range fixed { + lvl2stats[lvl] = srcs[0].Stats + src := srcs[0].Code + + sidx := strings.Index(src, start) + if sidx < 0 { + return nil, nil, fmt.Errorf("Fixed source doesn't contain start marker %q. Result:\n%s", start, src) + } + const end = `_ = "TEST CODE ENDS HERE"` + eidx := strings.Index(src, end) + if eidx < 0 { + return nil, nil, fmt.Errorf("Fixed source doesn't contain end marker %q. Result:\n%s", end, src) + } + + raw := strings.Split(src[sidx+len(start):eidx], "\n") + first := 0 + for ; first < len(raw); first++ { + if len(strings.TrimSpace(raw[first])) != 0 { + break + } + } + last := len(raw) - 1 + for ; last > 0; last-- { + if len(strings.TrimSpace(raw[last])) != 0 { + break + } + } + if first > last { + return nil, nil, fmt.Errorf("all output lines are empty in %q", raw) + } + var lines []string + for i := first; i <= last; i++ { + lines = append(lines, raw[i]) + } + + mintab := -1 + for i, ln := range lines { + if strings.TrimSpace(ln) == "" { + lines[i] = "" + continue + } + var w int + for _, ch := range ln { + if ch != '\t' { + break + } + w++ + } + if mintab == -1 || w < mintab { + mintab = w + } + } + for i, ln := range lines { + if ln != "" { + ln = ln[mintab:] + } + lines[i] = ln + } + + lvl2src[lvl] = strings.Join(lines, "\n") + } + + // Update Location information for all statistics so that line 1 is the + // first line introduced by the user. + var offset int + for _, ln := range strings.Split(src, "\n") { + offset++ + if strings.Contains(ln, "TEST CODE STARTS HERE") { + break + } + } + for _, stats := range lvl2stats { + for _, entry := range stats { + entry.GetLocation().GetStart().Line -= int64(offset) + entry.GetLocation().GetEnd().Line -= int64(offset) + } + } + + return lvl2src, lvl2stats, nil +} + +// A very simple test that verfies that fundamentals work: +// - fake objects are setup +// - test packages are setup +// - packages are setup and loaded +// - basic rewrites work +func TestSmokeTest(t *testing.T) { + tt := test{ + extra: `func g() string { return "" }`, + in: `m2.S = proto.String(g())`, + want: map[Level]string{ + Green: `m2.SetS(g())`, + }, + } + runTableTest(t, tt) +} + +func TestDoesntRewriteNonProtos(t *testing.T) { + src := `notAProto.S = proto.String(g())` + tt := test{ + extra: ` +type NotAProto struct { + S *string + Field struct{} +} +var notAProto *NotAProto +func g() string { return "" } +`, + in: src, + want: map[Level]string{ + Green: src, + }, + } + runTableTest(t, tt) +} + +// skip marks tests as skipped. +func skip(ts []test) []test { + for i := range ts { + ts[i].skip = "enable when fix.Rewrite is implemented" + } + return ts +} + +type test struct { + skip string // A reason to skip the test. Useful for disabling test-cases that don't work yet. + desc string + // Code added after the function enclosing the input. + extra string + // Input is wrapped in a package-level function. The package + // defines M2 and M3 as proto2 and proto3 messages + // respectively. m2 and m3 are variables of types *M2 and *M3 + // respectively. + in string + + // Name of the source file(s) to test with. Defaults to + // []string{"pkg_test.go"} if empty. + srcfiles []string + + typesToUpdate map[string]bool + builderTypes map[string]bool + + // Each test uses either want or wantRed but not both. + want map[Level]string + wantRed string // Used in tests that only do Red rewrites. + + // Asserts what expressions should be logged for gethering statistcs purposes. It may seem + // unnecessary to assert the result per level. This verifies that we don't lose type information + // necessary to calculate statistics. + wantStats map[Level][]*spb.Entry +} + +func runTableTest(t *testing.T, tt test) { + t.Helper() + + if tt.skip != "" { + t.Skip(tt.skip) + } + + in := NewSrc(tt.in, tt.extra) + srcfiles := tt.srcfiles + if len(srcfiles) == 0 { + srcfiles = []string{"pkg_test.go"} + } + for _, srcfile := range srcfiles { + cpkg := ConfiguredPackage{ + TypesToUpdate: tt.typesToUpdate, + BuilderTypes: tt.builderTypes, + } + got, _, err := fixSource(context.Background(), in, srcfile, cpkg, []Level{Green, Yellow, Red}) + if err != nil { + t.Fatalf("fixSource(%q) failed: %v; Full input:\n%s", tt.in, err, in) + } + failSrc := "<redacted because dumpSrcOnFail==false>" + if dumpSrcOnFail { + failSrc = in + } + + for _, lvl := range []Level{Green, Yellow, Red} { + want, ok := tt.want[lvl] + if !ok { + continue + } + want = trimNL(want) + if d := diff.Diff(want, got[lvl]); d != "" { + t.Errorf("fixSource(%q) = (%s) %q\nwant\n'%s'\ndiff:\n%s\nFull input source:\n------------------------------\n%s\n------------------------------\n", tt.in, lvl, got[lvl], want, d, failSrc) + } + } + } +} + +func runTableTests(t *testing.T, tests []test) { + t.Helper() + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + t.Helper() + runTableTest(t, tt) + }) + } +} + +// Verify that basic level calculation works. If this test fails than other tests are likely to be incorrect. +func TestLevels(t *testing.T) { + tt := test{ + extra: ` +func b() bool { return false } +func s() string { return "" } +func m() *pb2.M2 { return nil } +`, + in: ` +m2.S = proto.String("s") +m2.B, m2.S = proto.Bool(b()), proto.String(s()) +m2.S = m().S +`, + want: map[Level]string{ + Green: ` +m2.SetS("s") +m2.B, m2.S = proto.Bool(b()), proto.String(s()) +if x := m(); x.HasS() { + m2.SetS(x.GetS()) +} else { + m2.ClearS() +} +`, + // ignore evaluation order + Yellow: ` +m2.SetS("s") +m2.SetB(b()) +m2.SetS(s()) +if x := m(); x.HasS() { + m2.SetS(x.GetS()) +} else { + m2.ClearS() +} +`, + Red: ` +m2.SetS("s") +m2.SetB(b()) +m2.SetS(s()) +if x := m(); x.HasS() { + m2.SetS(x.GetS()) +} else { + m2.ClearS() +} +`, + }, + } + + runTableTest(t, tt) +}
diff --git a/internal/fix/rules_commonload_test.go b/internal/fix/rules_commonload_test.go new file mode 100644 index 0000000..86e4177 --- /dev/null +++ b/internal/fix/rules_commonload_test.go
@@ -0,0 +1,95 @@ +// 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 fix + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" +) + +type jsonPackage struct { + ImportPath string + Dir string + Name string + Export string + GoFiles []string + + Error struct { + Err string + } +} + +func goListBase() (*fakeLoaderBase, error) { + goList := exec.Command("go", "list", "-e", + "-json=ImportPath,Error,Dir,GoFiles,Export", + "-export=true", + "-deps=true", + // https://cs.opensource.google/go/x/tools/+/master:go/packages/golist.go;l=818-824;drc=977f6f71501a7b7b9b35d6125bf740401be8ce29 + "-pgo=off", + "google.golang.org/open2opaque/internal/fix/testdata/fake") + goList.Stderr = os.Stderr + stdout, err := goList.Output() + if err != nil { + return nil, err + } + + // Filter the packages for which we store data and load files. This reduces + // memory usage and makes the program easier to debug. + interestingPackages := map[string]bool{ + // Imported by testdata/fake/fake.go: + "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto": true, + "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto": true, + "google.golang.org/protobuf/proto": true, + "google.golang.org/protobuf/types/gofeaturespb": true, + // Imported by .pb.go files: + "google.golang.org/protobuf/reflect/protoreflect": true, + "google.golang.org/protobuf/runtime/protoimpl": true, + } + + importPathToFiles := make(map[string][]string) + importPathToExport := make(map[string][]byte) + for dec := json.NewDecoder(bytes.NewBuffer(stdout)); dec.More(); { + p := new(jsonPackage) + if err := dec.Decode(p); err != nil { + return nil, fmt.Errorf("JSON decoding failed: %v", err) + } + if !interestingPackages[p.ImportPath] { + continue + } + goFiles := make([]string, len(p.GoFiles)) + for idx, fn := range p.GoFiles { + goFiles[idx] = filepath.Join(p.Dir, fn) + } + importPathToFiles[p.ImportPath] = goFiles + b, err := os.ReadFile(p.Export) + if err != nil { + return nil, err + } + importPathToExport[p.ImportPath] = b + } + + pathToContent := make(map[string]string) + for _, goFiles := range importPathToFiles { + for _, fn := range goFiles { + b, err := os.ReadFile(fn) + if err != nil { + return nil, err + } + pathToContent[fn] = string(b) + } + } + + return &fakeLoaderBase{ + ImportPathToFiles: importPathToFiles, + PathToContent: pathToContent, + ExportFor: func(importPath string) []byte { + return importPathToExport[importPath] + }, + }, nil +}
diff --git a/internal/fix/rules_test.go b/internal/fix/rules_test.go new file mode 100644 index 0000000..dc32b52 --- /dev/null +++ b/internal/fix/rules_test.go
@@ -0,0 +1,1599 @@ +// 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 fix + +import ( + "context" + "strings" + "testing" + + "github.com/kylelemons/godebug/diff" +) + +func TestCommon(t *testing.T) { + tests := []test{{ + desc: "ignore messages that opted-out", + in: ` +m := &pb2.DoNotMigrateMe{B: proto.Bool(true)} +_ = *m.B +_ = m.B != nil +m.B = nil +`, + want: map[Level]string{ + Green: ` +m := &pb2.DoNotMigrateMe{B: proto.Bool(true)} +_ = *m.B +_ = m.B != nil +m.B = nil +`, + }, + }, { + desc: "proto2: set new empty message", + in: "m2.M = &pb2.M2{}", + want: map[Level]string{ + Green: "m2.SetM(&pb2.M2{})", + }, + }, { + desc: "ignore custom types", + extra: ` +type T1 pb2.M2 +type T2 *pb2.M2 +`, + in: ` +m := &T1{M: nil} +_ = *m.I64 +_ = m.I64 +m.I64 = nil +_ = m.I64 == nil + +var n T2 +_ = *n.I64 +_ = n.I64 +n.I64 = nil +_ = n.I64 == nil +`, + want: map[Level]string{ + Red: ` +m := &T1{M: nil} +_ = *m.I64 +_ = m.I64 +m.I64 = nil +_ = m.I64 == nil + +var n T2 +_ = *n.I64 +_ = n.I64 +n.I64 = nil +_ = n.I64 == nil +`, + }, + }, { + desc: "proto2: set new non-empty message", + in: `m2.M = &pb2.M2{S: proto.String("hello")}`, + want: map[Level]string{ + Green: `m2.SetM(pb2.M2_builder{S: proto.String("hello")}.Build())`, + }, + }, { + desc: "proto2: set existing message", + in: "m2.M = m2", + want: map[Level]string{ + Green: "m2.SetM(m2)", + }, + }, { + desc: "proto2: set message field", + in: "m2.M = m2.M", + want: map[Level]string{ + Green: "m2.SetM(m2.GetM())", + }, + }, { + desc: "proto2: set message function result", + extra: "func g2() *pb2.M2 { return nil }", + in: "m2.M = g2()", + want: map[Level]string{ + Green: "m2.SetM(g2())", + }, + }, { + desc: "proto2: set scalar slice", + in: "m2.Is = []int32{1,2,3}", + want: map[Level]string{ + Green: "m2.SetIs([]int32{1, 2, 3})", + }, + }, { + desc: "proto2: set scalar field, nohelper", + extra: `var s = new(string)`, + in: `m2.S = s // eol comment`, + want: map[Level]string{ + Yellow: `m2.S = s // eol comment`, + Red: `// eol comment +if s != nil { + m2.SetS(*s) +} else { + m2.ClearS() +}`, + }, + }, { + desc: "proto2: set bytes field", + in: ` +var b []byte +m2.Bytes = b +if m2.Bytes = b; false { +} +`, + want: map[Level]string{ + Green: ` +var b []byte +if b != nil { + m2.SetBytes(b) +} else { + m2.ClearBytes() +} +if m2.SetBytes(b); false { +} +`, + }, + }, { + desc: "proto2: set bytes field with func call", + in: ` +m2.Bytes = returnBytes() +`, + extra: ` +func returnBytes() []byte { return nil } +`, + want: map[Level]string{ + Green: ` +if x := returnBytes(); x != nil { + m2.SetBytes(x) +} else { + m2.ClearBytes() +} +`, + }, + }, { + desc: "proto3: set bytes field", + in: ` +var b []byte +m3.Bytes = b +if m3.Bytes = b; false { +} +`, + want: map[Level]string{ + Green: ` +var b []byte +m3.SetBytes(b) +if m3.SetBytes(b); false { +} +`, + }, + }, { + desc: "proto2: set scalar field, nohelper, addr", + extra: `var s = ""`, + in: `m2.S = &s`, + want: map[Level]string{ + Green: `m2.SetS(s)`, + }, + }, { + desc: "proto2: set proto.Bool", + in: `m2.B = proto.Bool(true)`, + want: map[Level]string{ + Green: `m2.SetB(true)`, + }, + }, { + desc: "proto2: set proto.Float32", + in: `m2.F32 = proto.Float32(42)`, + want: map[Level]string{ + Green: `m2.SetF32(42)`, + }, + }, { + desc: "proto2: set proto.Float64", + in: `m2.F64 = proto.Float64(42)`, + want: map[Level]string{ + Green: `m2.SetF64(42)`, + }, + }, { + desc: "proto2: set proto.Int", + in: `m2.I32 = proto.Int32(42)`, + want: map[Level]string{ + Green: `m2.SetI32(42)`, + }, + }, { + desc: "proto2: set proto.Int val", + extra: `var v int`, + in: `m2.I32 = proto.Int32(int32(v))`, + want: map[Level]string{ + Green: `m2.SetI32(int32(v))`, + }, + }, { + desc: "proto2: set proto.Int new", + in: `m2.I32 = new(int32)`, + want: map[Level]string{ + Green: `m2.SetI32(0)`, + }, + }, { + desc: "proto2: set enum", + in: `m2.E = pb2.M2_E_VAL.Enum()`, + want: map[Level]string{ + Green: `m2.SetE(pb2.M2_E_VAL)`, + }, + }, { + desc: "proto2: set enum new", + in: `m2.E = new(pb2.M2_Enum)`, + want: map[Level]string{ + Green: `m2.SetE(pb2.M2_Enum(0))`, + }, + }, { + desc: "proto2: set proto.Int32", + in: `m2.I32 = proto.Int32(42)`, + want: map[Level]string{ + Green: `m2.SetI32(42)`, + }, + }, { + desc: "proto2: set proto.Int64", + in: `m2.I64 = proto.Int64(42)`, + want: map[Level]string{ + Green: `m2.SetI64(42)`, + }, + }, { + desc: "proto2: set proto.String", + in: `m2.S = proto.String("q")`, + want: map[Level]string{ + Green: `m2.SetS("q")`, + }, + }, { + desc: "proto2: set proto.Uint32", + in: `m2.Ui32 = proto.Uint32(42)`, + want: map[Level]string{ + Green: `m2.SetUi32(42)`, + }, + }, { + desc: "proto2: set proto.Uint64", + in: `m2.Ui64 = proto.Uint64(42)`, + want: map[Level]string{ + Green: `m2.SetUi64(42)`, + }, + }, { + desc: "proto2: scalar field copy", + in: `m2.S = m2a.S // eol comment`, + want: map[Level]string{ + Green: ` +// eol comment +if m2a.HasS() { + m2.SetS(m2a.GetS()) +} else { + m2.ClearS() +} +`, + }, + }, { + desc: "proto2: scalar field copy, lhs/rhs identical", + in: `m2.S = m2.S // eol comment`, + want: map[Level]string{ + Green: ` +// eol comment +if m2.HasS() { + m2.SetS(m2.GetS()) +} else { + m2.ClearS() +} +`, + }, + }, { + desc: "proto3: set new empty message", + in: `m3.M = &pb3.M3{}`, + want: map[Level]string{ + Green: `m3.SetM(&pb3.M3{})`, + }, + }, { + desc: "proto3: set new non-empty message", + in: `m3.M = &pb3.M3{S:"s"}`, + want: map[Level]string{ + Green: `m3.SetM(pb3.M3_builder{S: "s"}.Build())`, + }, + }, { + desc: "proto3: set existing message", + in: "m3.M = m3", + want: map[Level]string{ + Green: "m3.SetM(m3)", + }, + }, { + desc: "proto3: set message field", + in: "m3.M = m3.M", + want: map[Level]string{ + Green: "m3.SetM(m3.GetM())", + }, + }, { + desc: "proto3: set message field new", + in: "m3.M = new(pb3.M3)", + want: map[Level]string{ + Green: "m3.SetM(new(pb3.M3))", + }, + }, { + desc: "proto3: set message function result", + extra: "func g3() *pb3.M3 { return nil }", + in: "m3.M = g3()", + want: map[Level]string{ + Green: "m3.SetM(g3())", + }, + }, { + desc: "proto3: set scalar slice", + in: "m3.Is = []int32{1,2,3}", + want: map[Level]string{ + Green: "m3.SetIs([]int32{1, 2, 3})", + }, + }, { + desc: "deref set", + extra: ` +var s string +var i32 int32 +type S struct { + Proto *pb2.M2 +} +`, + in: ` +*m2.S = "hello" +*m2.I32 = 1 +*m2.S = s +*m2.I32 = i32 + +*m2.M.S = "hello" +*m2.M.I32 = 1 +*m2.M.S = s +*m2.M.I32 = i32 + +ss := &S{} +*ss.Proto.M.S = "hello" +*ss.Proto.M.I32 = 1 +*ss.Proto.M.S = s +*ss.Proto.M.I32 = i32 +`, + want: map[Level]string{ + Green: ` +m2.SetS("hello") +m2.SetI32(1) +m2.SetS(s) +m2.SetI32(i32) + +m2.GetM().SetS("hello") +m2.GetM().SetI32(1) +m2.GetM().SetS(s) +m2.GetM().SetI32(i32) + +ss := &S{} +ss.Proto.GetM().SetS("hello") +ss.Proto.GetM().SetI32(1) +ss.Proto.GetM().SetS(s) +ss.Proto.GetM().SetI32(i32) +`, + Red: ` +m2.SetS("hello") +m2.SetI32(1) +m2.SetS(s) +m2.SetI32(i32) + +m2.GetM().SetS("hello") +m2.GetM().SetI32(1) +m2.GetM().SetS(s) +m2.GetM().SetI32(i32) + +ss := &S{} +ss.Proto.GetM().SetS("hello") +ss.Proto.GetM().SetI32(1) +ss.Proto.GetM().SetS(s) +ss.Proto.GetM().SetI32(i32) +`, + }, + }, { + desc: "proto3: scalar field copy", + in: `m3.S = m3.S`, + want: map[Level]string{ + Green: `m3.SetS(m3.GetS())`, + }, + }, { + desc: "proto2: clear message", + in: "m2.M = nil", + want: map[Level]string{ + Green: "m2.ClearM()", + }, + }, { + desc: "proto2: clear scalar", + in: "m2.S = nil", + want: map[Level]string{ + Green: "m2.ClearS()", + }, + }, { + desc: "proto2: clear enum", + in: "m2.E = nil", + want: map[Level]string{ + Green: "m2.ClearE()", + }, + }, { + desc: "proto2: clear bytes", + in: "m2.Bytes = nil", + want: map[Level]string{ + Green: "m2.ClearBytes()", + }, + }, { + desc: "proto2: clear scalar slice", + in: "m2.Is = nil", + want: map[Level]string{ + Green: "m2.SetIs(nil)", + }, + }, { + desc: "proto2: clear message slice", + in: "m2.Ms = nil", + want: map[Level]string{ + Green: "m2.SetMs(nil)", + }, + }, { + desc: "proto2: clear map", + in: "m2.Map = nil", + want: map[Level]string{ + Green: "m2.SetMap(nil)", + }, + }, { + desc: "proto3: clear message", + in: "m3.M = nil", + want: map[Level]string{ + Green: "m3.ClearM()", + }, + }, { + desc: "proto3: clear bytes", + in: "m3.Bytes = nil", + want: map[Level]string{ + Green: "m3.SetBytes(nil)", + }, + }, { + desc: "proto3 value: clear bytes", + extra: "var m3val pb3.M3", + in: "m3val.Bytes = nil", + want: map[Level]string{ + Green: "m3val.SetBytes(nil)", + }, + }, { + desc: "proto3: clear scalar slice", + in: "m3.Is = nil", + want: map[Level]string{ + Green: "m3.SetIs(nil)", + }, + }, { + desc: "proto3: clear message slice", + in: "m3.Ms = nil", + want: map[Level]string{ + Green: "m3.SetMs(nil)", + }, + }, { + desc: "proto3: clear map", + in: "m3.Map = nil", + want: map[Level]string{ + Green: "m3.SetMap(nil)", + }, + }, { + desc: "proto2: get message ptr", + in: "_ = m2.M", + want: map[Level]string{ + Green: "_ = m2.GetM()", + }, + }, { + desc: "proto2: get message value", + in: "_ = *m2.M", + want: map[Level]string{ + Green: "_ = *m2.GetM()", + }, + }, { + desc: "proto2: get scalar ptr", + in: `_ = m2.S`, + want: map[Level]string{ + Green: `_ = m2.S`, + Yellow: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`, + Red: `_ = proto.ValueOrNil(m2.HasS(), m2.GetS)`, + }, + }, { + desc: "proto2: get scalar ptr from side effect free expr", + in: `_ = m2.Ms[0].I32`, + want: map[Level]string{ + Green: `_ = m2.GetMs()[0].I32`, + Yellow: `_ = proto.ValueOrNil(m2.GetMs()[0].HasI32(), m2.GetMs()[0].GetI32)`, + Red: `_ = proto.ValueOrNil(m2.GetMs()[0].HasI32(), m2.GetMs()[0].GetI32)`, + }, + }, { + desc: "proto2: get scalar ptr from side effect expr (index)", + extra: `func f() int { return 0 }`, + in: `_ = m2.Ms[f()].I32`, + want: map[Level]string{ + Green: `_ = m2.GetMs()[f()].I32`, + Yellow: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(m2.GetMs()[f()])`, + Red: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(m2.GetMs()[f()])`, + }, + }, { + desc: "proto2: get scalar ptr from side effect expr (receiver)", + extra: `func f() []*pb2.M2 { return nil }`, + in: `_ = f()[0].I32`, + want: map[Level]string{ + Green: `_ = f()[0].I32`, + Yellow: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f()[0])`, + Red: `_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f()[0])`, + }, + }, { + desc: "proto2: get enum ptr", + in: `_ = m2.E`, + want: map[Level]string{ + Green: `_ = m2.E`, + Yellow: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`, + Red: `_ = proto.ValueOrNil(m2.HasE(), m2.GetE)`, + }, + }, { + desc: "proto2: get scalar value", + in: "_ = *m2.S", + want: map[Level]string{ + Green: "_ = m2.GetS()", + }, + }, { + desc: "proto2: scalar slice", + in: "_ = m2.Is", + want: map[Level]string{ + Green: "_ = m2.GetIs()", + }, + }, { + desc: "proto2: scalar slice and index", + in: "_ = m2.Is[0]", + want: map[Level]string{ + Green: "_ = m2.GetIs()[0]", + }, + }, { + desc: "proto2: message slice", + in: "_ = m2.Ms", + want: map[Level]string{ + Green: "_ = m2.GetMs()", + }, + }, { + desc: "proto2: message slice and index", + in: "_ = m2.Ms[0]", + want: map[Level]string{ + Green: "_ = m2.GetMs()[0]", + }, + }, { + desc: "proto2: get in function args", + extra: "func g2(*string, string, *pb2.M2, []int32, []*pb2.M2) { }", + in: "g2(m2.S, *m2.S, m2.M, m2.Is, m2.Ms)", + want: map[Level]string{ + Green: "g2(m2.S, m2.GetS(), m2.GetM(), m2.GetIs(), m2.GetMs())", + }, + }, { + desc: "proto2 assignments: nested", + in: ` +_ = func() int { + m2.I32 = proto.Int32(23) + return 0 +}() +`, + want: map[Level]string{ + Red: ` +_ = func() int { + m2.SetI32(23) + return 0 +}() +`, + }, + }, { + desc: "proto2 assignments: no side-effects", + extra: ` +var cnt int +func f2() *pb2.M2 { + cnt++ + return nil +} +`, + in: ` +newM := f2() +m2.B = newM.B +m2.GetM().B = newM.GetM().B +m2.M.B = newM.M.B + +type E struct { + Proto *pb2.M2 +} +e := &E{} +e.Proto.B = newM.B +e.Proto.GetM().B = newM.GetM().B +e.Proto.M.B = newM.M.B +m2.B = e.Proto.B +m2.GetM().B = e.Proto.GetM().B +m2.M.B = e.Proto.M.B +`, + + want: map[Level]string{ + Red: ` +newM := f2() +if newM.HasB() { + m2.SetB(newM.GetB()) +} else { + m2.ClearB() +} +if newM.GetM().HasB() { + m2.GetM().SetB(newM.GetM().GetB()) +} else { + m2.GetM().ClearB() +} +if newM.GetM().HasB() { + m2.GetM().SetB(newM.GetM().GetB()) +} else { + m2.GetM().ClearB() +} + +type E struct { + Proto *pb2.M2 +} +e := &E{} +if newM.HasB() { + e.Proto.SetB(newM.GetB()) +} else { + e.Proto.ClearB() +} +if newM.GetM().HasB() { + e.Proto.GetM().SetB(newM.GetM().GetB()) +} else { + e.Proto.GetM().ClearB() +} +if newM.GetM().HasB() { + e.Proto.GetM().SetB(newM.GetM().GetB()) +} else { + e.Proto.GetM().ClearB() +} +if e.Proto.HasB() { + m2.SetB(e.Proto.GetB()) +} else { + m2.ClearB() +} +if e.Proto.GetM().HasB() { + m2.GetM().SetB(e.Proto.GetM().GetB()) +} else { + m2.GetM().ClearB() +} +if e.Proto.GetM().HasB() { + m2.GetM().SetB(e.Proto.GetM().GetB()) +} else { + m2.GetM().ClearB() +} +`, + }}, { + desc: "proto2 assignments: side-effects rhs", + extra: ` +var cnt int +func f2() *pb2.M2 { + cnt++ + return nil +} +`, + in: ` +m2.B = f2().B // eol comment +`, + want: map[Level]string{ + Green: ` +// eol comment +if x := f2(); x.HasB() { + m2.SetB(x.GetB()) +} else { + m2.ClearB() +} +`}}, { + desc: "proto2 assignments: side-effects lhs", + extra: ` +var cnt int +func f2() *pb2.M2 { + cnt++ + return nil +} +`, + in: ` +f2().B = m2.B +`, + want: map[Level]string{ + Green: ` +if m2.HasB() { + f2().SetB(m2.GetB()) +} else { + f2().ClearB() +} +`}}, { + desc: "proto2 assignments: side-effects lhs and rhs", + extra: ` +var cnt int +func f2() *pb2.M2 { + cnt++ + return nil +} +`, + in: ` +f2().B = f2().B +`, + want: map[Level]string{ + Green: ` +if x := f2(); x.HasB() { + f2().SetB(x.GetB()) +} else { + f2().ClearB() +} +`}}, { + desc: "assign []byte", + extra: "var v string", + in: ` +m2.Bytes = []byte("hello") +m2.Bytes = []byte(v) +`, + want: map[Level]string{ + Green: ` +m2.SetBytes([]byte("hello")) +m2.SetBytes([]byte(v)) +`, + }, + }, { + desc: "increment non-proto", + in: ` +for i := 0; i < 10; i++ { +} +`, + want: map[Level]string{ + Green: ` +for i := 0; i < 10; i++ { +} +`, Red: ` +for i := 0; i < 10; i++ { +} +`, + }, + }, { + desc: "proto2: don't call methods on non-addressable receiver", + extra: ` +func f2() pb2.M2{ return pb2.M2{} } +`, + in: ` +_ = f2().B +_ = f2().Bytes +_ = f2().F32 +_ = f2().F64 +_ = f2().I32 +_ = f2().I64 +_ = f2().Ui32 +_ = f2().Ui64 +_ = f2().S +_ = f2().M +_ = f2().Is +_ = f2().Ms +_ = f2().Map +_ = f2().E + +_ = f2().B != nil +_ = f2().Bytes != nil +_ = f2().F32 != nil +_ = f2().F64 != nil +_ = f2().I32 != nil +_ = f2().I64 != nil +_ = f2().Ui32 != nil +_ = f2().Ui64 != nil +_ = f2().S != nil +_ = f2().M != nil +_ = f2().Is != nil +_ = f2().Ms != nil +_ = f2().Map != nil +_ = f2().E != nil + +if f2().B != nil { +} +`, + want: map[Level]string{ + Green: ` +_ = f2().B +_ = f2().Bytes +_ = f2().F32 +_ = f2().F64 +_ = f2().I32 +_ = f2().I64 +_ = f2().Ui32 +_ = f2().Ui64 +_ = f2().S +_ = f2().M +_ = f2().Is +_ = f2().Ms +_ = f2().Map +_ = f2().E + +_ = f2().B != nil +_ = f2().Bytes != nil +_ = f2().F32 != nil +_ = f2().F64 != nil +_ = f2().I32 != nil +_ = f2().I64 != nil +_ = f2().Ui32 != nil +_ = f2().Ui64 != nil +_ = f2().S != nil +_ = f2().M != nil +_ = f2().Is != nil +_ = f2().Ms != nil +_ = f2().Map != nil +_ = f2().E != nil + +if f2().B != nil { +} +`, + Red: ` +_ = func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2()) +_ = f2().GetBytes() +_ = func(msg *pb2.M2) *float32 { return proto.ValueOrNil(msg.HasF32(), msg.GetF32) }(f2()) +_ = func(msg *pb2.M2) *float64 { return proto.ValueOrNil(msg.HasF64(), msg.GetF64) }(f2()) +_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f2()) +_ = func(msg *pb2.M2) *int64 { return proto.ValueOrNil(msg.HasI64(), msg.GetI64) }(f2()) +_ = func(msg *pb2.M2) *uint32 { return proto.ValueOrNil(msg.HasUi32(), msg.GetUi32) }(f2()) +_ = func(msg *pb2.M2) *uint64 { return proto.ValueOrNil(msg.HasUi64(), msg.GetUi64) }(f2()) +_ = func(msg *pb2.M2) *string { return proto.ValueOrNil(msg.HasS(), msg.GetS) }(f2()) +_ = f2().GetM() +_ = f2().GetIs() +_ = f2().GetMs() +_ = f2().GetMap() +_ = func(msg *pb2.M2) *pb2.M2_Enum { return proto.ValueOrNil(msg.HasE(), msg.GetE) }(f2()) + +_ = func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2()) != nil +_ = f2().HasBytes() +_ = func(msg *pb2.M2) *float32 { return proto.ValueOrNil(msg.HasF32(), msg.GetF32) }(f2()) != nil +_ = func(msg *pb2.M2) *float64 { return proto.ValueOrNil(msg.HasF64(), msg.GetF64) }(f2()) != nil +_ = func(msg *pb2.M2) *int32 { return proto.ValueOrNil(msg.HasI32(), msg.GetI32) }(f2()) != nil +_ = func(msg *pb2.M2) *int64 { return proto.ValueOrNil(msg.HasI64(), msg.GetI64) }(f2()) != nil +_ = func(msg *pb2.M2) *uint32 { return proto.ValueOrNil(msg.HasUi32(), msg.GetUi32) }(f2()) != nil +_ = func(msg *pb2.M2) *uint64 { return proto.ValueOrNil(msg.HasUi64(), msg.GetUi64) }(f2()) != nil +_ = func(msg *pb2.M2) *string { return proto.ValueOrNil(msg.HasS(), msg.GetS) }(f2()) != nil +_ = f2().HasM() +_ = f2().GetIs() != nil +_ = f2().GetMs() != nil +_ = f2().GetMap() != nil +_ = func(msg *pb2.M2) *pb2.M2_Enum { return proto.ValueOrNil(msg.HasE(), msg.GetE) }(f2()) != nil + +if func(msg *pb2.M2) *bool { return proto.ValueOrNil(msg.HasB(), msg.GetB) }(f2()) != nil { +} +`, + }, + }, { + desc: "proto3: don't call methods on non-addressable receiver", + extra: ` +func f3() pb3.M3{ return pb3.M3{} } +`, + in: ` +_ = f3().B +_ = f3().Bytes +_ = f3().F32 +_ = f3().F64 +_ = f3().I32 +_ = f3().I64 +_ = f3().Ui32 +_ = f3().Ui64 +_ = f3().S +_ = f3().M +_ = f3().Is +_ = f3().Ms +_ = f3().Map + +_ = f3().Bytes != nil +_ = f3().M != nil +_ = f3().Is != nil +_ = f3().Ms != nil +_ = f3().Map != nil +`, + want: map[Level]string{ + Green: ` +_ = f3().B +_ = f3().Bytes +_ = f3().F32 +_ = f3().F64 +_ = f3().I32 +_ = f3().I64 +_ = f3().Ui32 +_ = f3().Ui64 +_ = f3().S +_ = f3().M +_ = f3().Is +_ = f3().Ms +_ = f3().Map + +_ = len(f3().Bytes) != 0 +_ = f3().M != nil +_ = f3().Is != nil +_ = f3().Ms != nil +_ = f3().Map != nil +`, + Red: ` +_ = f3().GetB() +_ = f3().GetBytes() +_ = f3().GetF32() +_ = f3().GetF64() +_ = f3().GetI32() +_ = f3().GetI64() +_ = f3().GetUi32() +_ = f3().GetUi64() +_ = f3().GetS() +_ = f3().GetM() +_ = f3().GetIs() +_ = f3().GetMs() +_ = f3().GetMap() + +_ = len(f3().GetBytes()) != 0 +_ = f3().HasM() +_ = f3().GetIs() != nil +_ = f3().GetMs() != nil +_ = f3().GetMap() != nil +`, + }, + }} + + runTableTests(t, tests) +} + +func TestShallowCopies(t *testing.T) { + // Shallow copy rewrites lose scalar field aliasing. All are red rewrites. + + // https://golang.org/ref/spec#Address_operators + // "For an operand x of type T, the address operation &x generates a + // pointer of type *T to x. The operand must be addressable, that is, + // either a variable, pointer indirection, or slice indexing operation; + // or a field selector of an addressable struct operand; or an array + // indexing operation of an addressable array." + tests := skip([]test{{ + desc: `definition, rhs is addressable`, + in: ` +m := *m2 +_ = &m +`, + wantRed: ` +var m pb2.M2 +proto.Assign(&m, m2) +_ = &m +`, + }, { + desc: `definition, rhs is not addressable`, + extra: `func g() pb2.M2 { return pb2.M2{} }`, + in: ` +m := g() +_ = &m +`, + wantRed: ` +m := g() +_ = &m +`, + }, { // Both lhs (left-hand side) and rhs (right-hand side) are addressable. + desc: `proto2: lhs is addressable, rhs is an empty composite literal`, + extra: `var m pb2.M2`, + in: `m = pb2.M2{}`, + wantRed: `proto.Assign(&m, &pb2.M2{})`, + }, { + desc: `proto3: lhs is addressable, rhs is an empty composite literal`, + extra: `var m pb3.M3`, + in: `m = pb3.M3{}`, + wantRed: `proto.Assign(&m, &pb3.M3{})`, + }, { + desc: `lhs is addressable, rhs is non-empty composite literal`, + extra: `var m pb2.M2`, + in: `m = pb2.M2{M: nil}`, + wantRed: `proto.Assign(&m, pb2.M2_builder{M: nil}.Build())`, + }, { + desc: `lhs and rhs are addressable: pointer indirections`, + in: `*m2 = *m2`, + wantRed: `proto.Assign(m2, m2)`, + }, { + desc: `lhs and rhs are addressable: pointer indirection 2`, + extra: `var m pb2.M2; func mp() *pb2.M2 { return nil }`, + in: `m = *mp()`, + wantRed: `proto.Assign(&m, mp())`, + }, { + desc: `lhs and rhs are addressable: variables`, + extra: `var m pb2.M2`, + in: `m = m`, + wantRed: `proto.Assign(&m, &m)`, + }, { + desc: `lhs and rhs are addressable: addressable expr in parens`, + extra: `var m pb2.M2`, + in: `(m) = (m)`, + want: map[Level]string{ + Yellow: `m = m`, + Red: `proto.Assign(&m, &m)`, + }, + }, { + desc: `lhs and rhs are addressable: addressable slice index`, + extra: `var ms = []pb2.M2{}`, + in: `ms[0] = ms[0]`, + wantRed: `proto.Assign(&ms[0], &ms[0])`, + }, { + desc: `lhs and rhs are addressable: non-addressable slice index`, + extra: `func s() []pb2.M2 { return nil }`, + in: `s()[0] = s()[0]`, + wantRed: `proto.Assign(&s()[0], &s()[0])`, + }, { + desc: `lhs and rhs are addressable: addressable array index`, + extra: `var ms [1]pb2.M2`, + in: `ms[0] = ms[0]`, + wantRed: `proto.Assign(&ms[0], &ms[0])`, + }, { + desc: `lhs and rhs are addressable: field selector`, + extra: `var t struct{m pb2.M2}`, + in: `t.m = t.m`, + wantRed: `proto.Assign(&t.m, &t.m)`, + }, { // Only rhs is addressable. + desc: `proto2: lhs is not addressable, rhs is an empty struct literal`, + extra: `var m = map[int]pb2.M2{}`, + in: `m[0] = pb2.M2{}`, + wantRed: `m[0] = pb2.M2{}`, + }, { + desc: `proto3: lhs is not addressable, rhs is an empty struct literal`, + extra: `var m = map[int]pb3.M3{}`, + in: `m[0] = pb3.M3{}`, + wantRed: `m[0] = pb3.M3{}`, + }, { + desc: `lhs is not addressable, rhs is a non-empty composite literal`, + extra: `var m = map[int]pb2.M2{}`, + in: `m[0] = pb2.M2{M: nil}`, + wantRed: `m[0] = *pb2.M2_builder{M: nil}.Build()`, + }, { + desc: `lhs is not addressable, rhs is addressable: pointer indirection`, + extra: `var m = map[int]pb2.M2{}`, + in: `m[0] = *m2`, + wantRed: `m[0] = *proto.Clone(m2).(*pb2.M2)`, + }, { + desc: `lhs is not addressable, rhs is addressable: addressable slice index`, + extra: `var m = map[int]pb2.M2{}; var ms []pb2.M2`, + in: `m[0] = ms[0]`, + wantRed: `m[0] = *proto.Clone(&ms[0]).(*pb2.M2)`, + }, { + desc: `lhs is not addressable, rhs is addressable: not-addressable slice index`, + extra: `var m = map[int]pb2.M2{}; func s() []pb2.M2{return nil}`, + in: `m[0] = s()[0]`, + wantRed: `m[0] = *proto.Clone(&s()[0]).(*pb2.M2)`, + }, { + desc: `lhs is not addressable, rhs is addressable: addressable array index`, + extra: `var m = map[int]pb2.M2{}; var ms [1]pb2.M2`, + in: `m[0] = ms[0]`, + wantRed: `m[0] = *proto.Clone(&ms[0]).(*pb2.M2)`, + }, { + desc: `lhs is not addressable, rhs is addressable: field selector`, + extra: `var m = map[int]pb2.M2{}; var t struct {m pb2.M2} `, + in: `m[0] = t.m`, + wantRed: `m[0] = *proto.Clone(&t.m).(*pb2.M2)`, + }, { + desc: `lhs is an underscore, rhs is addressable`, + in: `_ = *m2`, + wantRed: `_ = *proto.Clone(m2).(*pb2.M2)`, + }, { // Rhs is not addressable => no rewrite. + desc: `lhs is addressable, rhs is not addressable: map access`, + extra: `var m = map[int]pb2.M2{}`, + in: `*m2 = m[0]`, + wantRed: `*m2 = m[0]`, + }, { + desc: `lhs is addressable, rhs is not addressable: array index`, + extra: `func m() [1]pb2.M2 {return [1]pb2.M2{} }`, + in: `*m2 = m()[0]`, + wantRed: `*m2 = m()[0]`, + }, { + desc: `lhs is addressable, rhs is not addressable: func result`, + extra: `func m() pb2.M2 { return pb2.M2{} }`, + in: `*m2 = m()`, + wantRed: `*m2 = m()`, + }, { + desc: `lhs is addressable, rhs is not addressable: type conversion`, + in: `*m2 = pb2.M2(pb2.M2{})`, + wantRed: `*m2 = pb2.M2(pb2.M2{})`, + }, { + desc: `lhs is addressable, rhs is not addressable: chan receive`, + extra: `var ch chan pb2.M2`, + in: `*m2 = <-ch`, + wantRed: `*m2 = <-ch`, + }, { // Neither rhs nor lhs is addressable + desc: `lhs is not addressable, rhs is not addressable`, + extra: `var m = map[int]pb2.M2{}`, + in: `m[0] = m[0]`, + wantRed: `m[0] = m[0]`, + }, { // No lhs (not assignment context) + desc: `addressable function argument`, + extra: `func g(pb2.M2) {}; var m pb2.M2`, + in: `g(m)`, + wantRed: `g(*proto.Clone(&m).(*pb2.M2))`, + }, { + desc: `don't rewrite proto.Clone`, + extra: `func g(pb2.M2) {}; var m pb2.M2`, + in: `g(*proto.Clone(&m).(*pb2.M2))`, + wantRed: `g(*proto.Clone(&m).(*pb2.M2))`, + }, { + desc: `empty maker function argument`, + extra: `func g(pb2.M2) {}`, + in: `g(pb2.M2{})`, + wantRed: `g(pb2.M2{})`, + }, { + desc: `non-empty maker function argument`, + extra: `func g(pb2.M2) {}`, + in: `g(pb2.M2{M: nil})`, + wantRed: `g(*pb2.M2_builder{M: nil}.Build())`, + }, { + desc: `slice of values`, + in: `_ = []pb2.M2{{M: nil}, pb2.M2{M: nil}, pb2.M2{}, {}}`, + wantRed: `_ = []pb2.M2{*pb2.M2_builder{M: nil}.Build(), *pb2.M2_builder{M: nil}.Build(), pb2.M2{}, {}}`, + }, { + desc: `non-addressable function argument`, + extra: `func g(pb2.M2) {}; var m map[int]pb2.M2`, + in: `g(m[0])`, + wantRed: `g(m[0])`, + }}) + + runTableTests(t, tests) +} + +func TestProto2ScalarAliasing(t *testing.T) { + tests := skip([]test{{ + desc: "proto2: only def + alias", + skip: "make red rewrite work for scalar aliasing", + in: ` +s := "hello world" +m2.S = &s`, + want: map[Level]string{ + Yellow: ` +s := "hello world" +m2.S = &s`, + Red: ` +m2.SetS(s) +`, + }}, { + desc: "proto2: no access after alias", + skip: "make red rewrite work for scalar aliasing", + in: ` +s := "hello" +s = "world" +m2.S = &s`, + want: map[Level]string{ + Yellow: ` +s := "hello" +s = "world" +m2.S = &s`, + Red: ` +s := "hello" +s = "world" +m2.SetS(s) +`, + }}, { + desc: "proto2: only reads after alias", + skip: "make red rewrite work for scalar aliasing", + extra: "func g(string) { }", in: ` +s := "hello world" +m2.S = &s +g(s) +`, + want: map[Level]string{ + Yellow: ` +s := "hello world" +m2.S = &s +g(s) +`, + Red: ` +s := "hello world" +m2.SetS(s) +g(s) +`, + }}, { + extra: "var b = true", + desc: "proto2: conditionals prevent inlining", + skip: "make red rewrite work for scalar aliasing", + in: ` +s := "hello" +if b { + s = "world" +} +m2.S = &s +`, + want: map[Level]string{ + Yellow: ` +s := "hello" +if b { +s = "world" +} +m2.S = &s +`, + Red: ` +s := "hello" +if b { +s = "world" +} +m2.SetS(s) +`, + }}, { + desc: "aliasing of message fields", + skip: "make red rewrite work for scalar aliasing", + in: ` +n2 := m() +m2.S = n2.S +`, + want: map[Level]string{ + Yellow: ` +n2 := m() +m2.S = n2.S +`, + Red: ` +n2 := m() +m2.SetS(n2.GetS()) +`, + }, + }}) + + runTableTests(t, tests) +} + +func trimNL(s string) string { + if len(s) != 0 && s[0] == '\n' { + s = s[1:] + } + if len(s) != 0 && s[len(s)-1] == '\n' { + s = s[:len(s)-1] + } + return s +} + +func TestUnparentExpr(t *testing.T) { + tests := skip([]test{{ + desc: "proto2: clear scalar", + in: "(m2.S) = nil", + want: map[Level]string{ + Green: "m2.ClearS()", + }}, { + desc: "proto2: clear scalar in if", + in: ` +if true { + (m2.S) = nil +} +`, + want: map[Level]string{ + Green: ` +if true { + m2.ClearS() +} +`, + }}, { + desc: "proto2: clear scalar 2", + in: "((m2.S)) = nil", + want: map[Level]string{ + Green: "m2.ClearS()", + }}, { + desc: "proto2: clear message", + in: "(m2.M) = nil", + want: map[Level]string{ + Green: "m2.ClearM()", + }}, { + desc: "proto2: clear message 2", + in: "((m2.M)) = nil", + want: map[Level]string{ + Green: "m2.ClearM()", + }}, { + desc: "proto2: clear parenthesized scalar slice", + in: "(m2.Is) = nil", + want: map[Level]string{ + Green: "m2.SetIs(nil)", + }}, { + desc: "proto2: clear parenthesized message slice", + in: "(m2.Ms) = nil", + want: map[Level]string{ + Green: "m2.SetMs(nil)", + }}, { + desc: "proto2: clear parenthesized oneof", + in: "(m2.OneofField) = nil", + want: map[Level]string{ + Green: "m2.ClearOneofField()", + }}, { + desc: "proto2: get on lhs used for indexing", + in: ` +var ns []int +ns[*m2.I32] = 0 +ns[(*m2.I32)] = 0 +ns[*(m2.I32)] = 0 +`, + want: map[Level]string{ + Green: ` +var ns []int +ns[m2.GetI32()] = 0 +ns[m2.GetI32()] = 0 +ns[m2.GetI32()] = 0 +`, + }}, { + desc: "proto2: get on lhs used for indexing", + in: ` +var ns []int +ns[m3.I32] = 0 +ns[(m3.I32)] = 0 +`, + want: map[Level]string{ + Green: ` +var ns []int +ns[m3.GetI32()] = 0 +ns[m3.GetI32()] = 0 +`, + }}, { + desc: "proto3: clear message", + in: "(m3.M) = nil", + want: map[Level]string{ + Green: "m3.ClearM()", + }}, { + desc: "proto3: clear message 3", + in: "((m3.M)) = nil", + want: map[Level]string{ + Green: "m3.ClearM()", + }}, { + desc: "proto3: clear parenthesized scalar slice", + in: "(m3.Is) = nil", + want: map[Level]string{ + Green: "m3.SetIs(nil)", + }}, { + desc: "proto3: clear parenthesized message slice", + in: "(m3.Ms) = nil", + want: map[Level]string{ + Green: "m3.SetMs(nil)", + }}, { + desc: "proto3: clear parenthesized oneof", + in: "(m3.OneofField) = nil", + want: map[Level]string{ + Green: "m3.ClearOneofField()", + }}, + }) + + runTableTests(t, tests) +} + +func TestPreserveComments(t *testing.T) { + tests := []test{ + { + desc: "comments around CompositeLit", + in: ` +// Comment above. +_ = &pb2.M2{S:nil} // Inline comment. +// Comment below. +`, + want: map[Level]string{ + Green: ` +// Comment above. +_ = pb2.M2_builder{S: nil}.Build() // Inline comment. +// Comment below. +`, + }, + }, + + { + desc: "comments in CompositeLit", + in: ` +_ = &pb2.M2{ // Comment here. + // Comment above field S + S: nil, // Inside literal. +} // After. +`, + want: map[Level]string{ + Green: ` +_ = pb2.M2_builder{ // Comment here. + // Comment above field S + S: nil, // Inside literal. +}.Build() // After. +`, + }, + }, + + { + desc: "CompositeLit to setters", + srcfiles: []string{"nontest.go"}, + in: ` +_ = &pb2.M2{ // Comment here. + // Comment above field S + S: nil, // Inside literal. + // Comment above nested message M. + M: &pb2.M2{ + // Comment above field I32 in nested M. + I32: proto.Int32(32), + }, +} // After. +`, + want: map[Level]string{ + Green: ` +m2h2 := &pb2.M2{} +// Comment above field I32 in nested M. +m2h2.SetI32(32) +m2h3 := &pb2.M2{ // Comment here. +} +// Comment above field S +m2h3.ClearS() // Inside literal. +// Comment above nested message M. +m2h3.SetM(m2h2) +_ = m2h3 // After. +`, + }, + }, + + { + desc: "set clear has", + in: ` +// Comment 1 +// More comments +m2.S = proto.String("hello") // Comment 2 +// Comment 3 +m2.S = nil // Comment 4 +// Comment 5 +_ = m2.S != nil // Comment 6 +// Comment 7 +_ = m2.S == nil // Comment 8 +// Comment 9 +`, + want: map[Level]string{ + Green: ` +// Comment 1 +// More comments +m2.SetS("hello") // Comment 2 +// Comment 3 +m2.ClearS() // Comment 4 +// Comment 5 +_ = m2.HasS() // Comment 6 +// Comment 7 +_ = !m2.HasS() // Comment 8 +// Comment 9 +`, + }, + }, + + { + desc: "multi-assign", + in: ` +var n int +_ = n +// Comment 1 +n, m2.B, m2.S = 42, proto.Bool(true), proto.String("s") // Comment 2 +// Comment 3 +`, + want: map[Level]string{ + Yellow: ` +var n int +_ = n +// Comment 1 +n = 42 +m2.SetB(true) +m2.SetS("s") // Comment 2 +// Comment 3 +`, + }, + }, + + { + desc: "multi-line msg slices", + in: ` +_ = []*pb2.M2{ + // Comment 1 + &pb2.M2{}, // Comment 2 + &pb2.M2{M: nil}, // Comment 3 + // Comment 4 + &pb2.M2{ // Comment 5 + // Comment 6 + M: nil, // Comment 7 + }, // Comment 8 + // Comment 9 +} + +_ = []*pb3.M3{ + // Comment 1 + {}, // Comment 2 + {B: true}, // Comment 3 + // Comment 4 + { // Comment 5 + // Comment 6 + S: "hello", // Comment 7 + // Comment 8 + }, // Comment 9 +} +`, + want: map[Level]string{ + Green: ` +_ = []*pb2.M2{ + // Comment 1 + &pb2.M2{}, // Comment 2 + pb2.M2_builder{M: nil}.Build(), // Comment 3 + // Comment 4 + pb2.M2_builder{ // Comment 5 + // Comment 6 + M: nil, // Comment 7 + }.Build(), // Comment 8 + // Comment 9 +} + +_ = []*pb3.M3{ + // Comment 1 + {}, // Comment 2 + pb3.M3_builder{B: true}.Build(), // Comment 3 + // Comment 4 + pb3.M3_builder{ // Comment 5 + // Comment 6 + S: "hello", // Comment 7 + // Comment 8 + }.Build(), // Comment 9 +} +`, + }, + }, + + { + desc: "if init simple statement", + in: ` +// Comment 1 +if m3.S, m3.M = "", (&pb3.M3{}); m3.B { // Comment 2 + // Comment 3 + m3.B, m3.Is = true, nil // Comment 4 + // Comment 5 +} +// Comment 6 +`, + want: map[Level]string{ + Yellow: ` +m3.SetS("") +m3.SetM(&pb3.M3{}) + +// Comment 1 +if m3.GetB() { // Comment 2 + // Comment 3 + m3.SetB(true) + m3.SetIs(nil) // Comment 4 + // Comment 5 +} +// Comment 6 +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestAllowRenamingProtoPackage(t *testing.T) { + in := `package p + +import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto" +import protolib "google.golang.org/protobuf/proto" + +var _ = protolib.String + +func f() { + m := &pb2.M2{} + _ = "TEST CODE STARTS HERE" + _ = m.S + _ = "TEST CODE ENDS HERE" +} +` + want := `_ = protolib.ValueOrNil(m.HasS(), m.GetS)` + + got, _, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, []Level{Green, Yellow, Red}) + if err != nil { + t.Fatalf("fixSource() failed: %v\nFull source:\n%s\n------------------------------", err, in) + } + if d := diff.Diff(want, got[Red]); d != "" { + t.Errorf("fixSource(%q) = (red) %q; want %s\ndiff:\n%s\n", in, got, want, d) + } +} + +func TestAddsProtoImport(t *testing.T) { + // The m2.M assignment will be rewritten to: + // + // m2.SetM(pb2.M2_builder{S: proto.String(m2.GetS())}.Build()) + // + // Because the package does not currently import the proto package, + // a new proto import should be added. + const src = `// _ = "TEST CODE STARTS HERE" +package p + +import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto" + +func test_function() { + m2 := new(pb2.M2) + m2.M = pb2.M2_builder{S: m2.S}.Build() + _ = "TEST CODE ENDS HERE" +} +` + + gotAll, _, err := fixSource(context.Background(), src, "code.go", ConfiguredPackage{}, []Level{Green, Yellow, Red}) + if err != nil { + t.Fatalf("fixSource() failed: %v; Full input:\n%s", err, src) + } + got := gotAll[Red] + if !strings.Contains(got, "google.golang.org/protobuf/proto") { + t.Fatalf("proto import not added: %q", got) + } +}
diff --git a/internal/fix/stats.go b/internal/fix/stats.go new file mode 100644 index 0000000..dc1e7a5 --- /dev/null +++ b/internal/fix/stats.go
@@ -0,0 +1,1201 @@ +// 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 fix + +import ( + "fmt" + "go/token" + "go/types" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" + log "github.com/golang/glog" + "google.golang.org/open2opaque/internal/o2o/statsutil" + "google.golang.org/open2opaque/internal/protodetecttypes" + + spb "google.golang.org/open2opaque/internal/dashboard" +) + +// stats lists uses of protocol buffer types that are interesting from analysis +// standpoint. This function is called after all rewrites for level c.lvl are +// applied on the file. If generated is true, then Location.IsGeneratedFile +// will be set to true on the returned entries. +func stats(c *cursor, f *dst.File, generated bool) []*spb.Entry { + // Temporarily disable stats after rewrites. Only calculate for code before our changes. + if c.lvl != None { + return nil + } + parents := map[dst.Node]dst.Node{} + var out []*spb.Entry + dstutil.Apply(f, func(cur *dstutil.Cursor) bool { + n := cur.Node() + parents[n] = cur.Parent() + + switch x := n.(type) { + case nil: + // ApplyFunc can be called with a nil node, for example, when processing a + // function declaration + return false + case *dst.BadStmt, *dst.BadExpr, *dst.BadDecl, *dst.ImportSpec: + // Don't recurse into nodes that can't refer to proto types. + return false + case *dst.GenDecl: + // Only recurse into declarations that can reference protos. + return x.Tok != token.IMPORT + } + + switch n := n.(type) { + case *dst.SelectorExpr: + out = append(out, selectorStats(c, n, cur.Parent())...) + case *dst.CallExpr: + out = append(out, callStats(c, n, cur.Parent())...) + case *dst.AssignStmt: + out = append(out, assignStats(c, n, cur.Parent())...) + case *dst.CompositeLit: + out = append(out, compositeLitStats(c, n, cur.Parent())...) + case *dst.SendStmt: + out = append(out, sendStats(c, n, cur.Parent())...) + case *dst.ReturnStmt: + out = append(out, returnStats(c, n, cur.Parent(), parents)...) + case *dst.TypeAssertExpr: + out = append(out, typeAssertStats(c, n, cur.Parent())...) + case *dst.TypeSpec: + out = append(out, typeSpecStats(c, n, cur.Parent())...) + case *dst.StructType: + out = append(out, structStats(c, n, cur.Parent())...) + } + return true + }, nil) + if generated { + for _, e := range out { + e.GetLocation().IsGeneratedFile = true + } + } + return out +} + +func logSiloed(c *cursor, out []*spb.Entry, n, parent dst.Node) []*spb.Entry { + return append(out, &spb.Entry{ + Status: &spb.Status{ + Type: spb.Status_FAIL, + Error: "type information missing; are dependencies in a silo?", + }, + Location: location(c, n), + Level: toRewriteLevel(c.lvl), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", n), + ParentType: fmt.Sprintf("%T", parent), + }, + }) +} + +// logConversion appends an entry to the out slice if the conversion is an +// interesting one. +// +// Interesting conversions include all conversions that can make the open2opaque +// migration harder (e.g. converting protos to interface{}, unsafe.Pointer, +// etc.). +// +// Exactly one of srcExpr and src must be set. Those determine the +// type/expression that is being converted to type dst. +// +// The nodes n and parent build the context for the conversion. Parent should be +// the parent node of n in the AST. +// +// The use specifies the reason for the conversion (is it an implicit conversion +// to a function argument? explicit conversion in assignment? etc.). +func logConversion(c *cursor, out []*spb.Entry, srcExpr dst.Expr, src, dst types.Type, n, parent dst.Node, use *spb.Use) []*spb.Entry { + if (srcExpr != nil) == (src != nil) { + panic(fmt.Sprintf("logConversion: either srcExpr or src must set, but not both (srcExpr!=nil: %t, src!=nil: %t)", srcExpr != nil, src != nil)) + } + if src == nil { + src = c.typeOfOrNil(srcExpr) + } + if src == nil { + return logSiloed(c, out, n, parent) + } + if !isInterfaceType(dst) { + return out + } + t, ok := c.shouldLogCompositeType(src, true) + if !ok { + return out + } + return append(out, &spb.Entry{ + Location: location(c, n), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", n), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: use, + }) +} + +// logShallowCopy appends an entry to the out slice if the expression is a +// shallow copy. +// +// Exactly one of srcExpr and src must be set. Those determine the +// type/expression that is being converted to type dst. +// +// The nodes n and parent build the context for the conversion. Parent should be +// the parent node of n in the AST. +// +// The use specifies the reason for the conversion (is it an implicit conversion +// to a function argument? explicit conversion in assignment? etc.). +func logShallowCopy(c *cursor, out []*spb.Entry, srcExpr dst.Expr, src types.Type, n, parent dst.Node, use *spb.Use) []*spb.Entry { + if (srcExpr != nil) == (src != nil) { + panic(fmt.Sprintf("logShallowCopy: either srcExpr or src must set, but not both (srcExpr!=nil: %t, src!=nil: %t)", srcExpr != nil, src != nil)) + } + if src == nil { + src = c.typeOfOrNil(srcExpr) + } + if src == nil { + return logSiloed(c, out, n, parent) + } + + if _, isPtr := src.Underlying().(*types.Pointer); isPtr { + return out + } + t, ok := c.shouldLogCompositeType(src, false) + if !ok { + return out + } + + return append(out, &spb.Entry{ + Location: location(c, n), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", n), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: use, + }) +} + +// shouldLogCompositeType returns true if an expression should be logged when +// the composite type t is part of the expression. +// +// followPointers determines whether pointer types should be traversed when +// looking for the result. This is mainly useful for finding shallow copies. +// +// If shouldLogCompositeType returns true, then it also returns the reason why +// the type should be logged (i.e. the actual proto type referenced by the +// composite type t). +// +// For example, the following expression: +// +// f(struct{ // assume: func f(interface{}) { } +// m *pb.M2, +// }{ +// m: m2, +// }) +// +// should be logged (if *pb.M2 is a proto type that should be tracked) because a +// value of type *pb.M2 is converted to interface{}. +// +// The following expression should not be tracked: +// +// f(struct{ // assume: func f(interface{}) { } +// ch chan *pb.M2, +// }{ +// ch: make(chan *pb.M2), +// }) +// +// because it does not contain values of type *pb.M2. Only references to the +// type. (what should/shouldn't be tracked is a somewhat arbitrary choice). +// +// This functionality exists to statically track potential Go reflect usage on +// protos and shallow copies. +// +// NOTE: one could argue that it would be more accurate to track all references +// to proto types, not only those associated with values in expressions +// (e.g. the channel example above). We've tried that and the number of findings +// (false positives) is so large that the statistic becomes meaningless. +func (c *cursor) shouldLogCompositeType(t types.Type, followPointers bool) (out types.Type, _ bool) { + // We use a cache for two reasons: + // - (major) to handle cyclic data structures + // - (minor) to improve performance + // + // Consider type: + // + // type T struct { + // F *T + // } + // + // and shouldLogCompositeType call with T and followPointers=true. We + // don't want a recursive call shouldLogCompositeType for the field F + // after dereferencing the pointer as that would result in an infinite + // recursion. + // + // At the time we see T for the first time, we don't know if it will + // contain pointers or not, but we have to signal not to process T + // again. Our approach involves three states for T in the cache: + // + // 1. an empty cache for T means that we've never seen this type (and + // hence should process it) + // + // 2. a cache with a nil value for T means either that: + // + // - we're currently processing the type. We don't know whether it + // depends on protos or not, but we know that we shouldn't start + // processing it again; or + // + // - we've processed the type before and it doesn't reference + // protos. We can return nil as the result. + // + // 3. a cache with a non-nil value for T means that we've processed + // the type before and that we can simply return the result. + // + // The result of shouldLogCompositeType can be different for a single + // type, based on the followPointers value. Therefore, we have two + // caches: one for each possdible value of followPointers. Technically + // followPointers=false implies that there should be no infinite + // recursion (in the current version of shouldLogCompositeType) but we + // want to keep the code symmetric. + cache := c.shouldLogCompositeTypeCache + if followPointers { + cache = c.shouldLogCompositeTypeCacheNoPtr + } + + if cached := cache.At(t); cached != nil { + ce := cached.(*cacheEntry) + return ce.protoType, ce.protoType != nil + } + + cache.Set(t, &cacheEntry{}) + + defer func() { + cache.Set(t, &cacheEntry{protoType: out}) + }() + + if _, isPtr := t.Underlying().(*types.Pointer); !followPointers && isPtr { + return nil, false + } + if c.shouldTrackType(t) { + return t, true + } + switch t := t.(type) { + case *types.Tuple: + for i := 0; i < t.Len(); i++ { + if t, ok := c.shouldLogCompositeType(t.At(i).Type(), followPointers); ok { + return t, true + } + } + return nil, false + case *types.Interface: + // The interface could contain a proto, however we would've caught + // conversion to the interface type. + return nil, false + case *types.Named: + return c.shouldLogCompositeType(t.Underlying(), followPointers) + case *types.Pointer: + if !followPointers { + return nil, false + } + return c.shouldLogCompositeType(t.Elem(), followPointers) + case *types.Signature: + return nil, false + case *types.TypeParam: + return nil, false + case *types.Slice: + if !followPointers { + return nil, false + } + return c.shouldLogCompositeType(t.Elem(), followPointers) + case *types.Array: + return c.shouldLogCompositeType(t.Elem(), followPointers) + case *types.Basic: + return nil, false + case *types.Chan: + return nil, false + case *types.Map: + if !followPointers { + return nil, false + } + if t, ok := c.shouldLogCompositeType(t.Key(), followPointers); ok { + return t, true + } + if t, ok := c.shouldLogCompositeType(t.Elem(), followPointers); ok { + return t, true + } + return nil, false + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + if t, ok := c.shouldLogCompositeType(t.Field(i).Type(), followPointers); ok { + return t, true + } + } + return nil, false + case *types.Alias: + return c.shouldLogCompositeType(types.Unalias(t), followPointers) + default: + panic(fmt.Sprintf("unrecognized type %T", t)) + } +} + +// convToUnsafePointerArg returns the x expression in unsafe.Pointer(x) +// conversion if expr has the form unsafe.Pointer(x). +func convToUnsafePointerArg(c *cursor, expr dst.Expr) (dst.Expr, bool) { + call, ok := expr.(*dst.CallExpr) + if !ok { + return nil, false + } + if sel, ok := call.Fun.(*dst.SelectorExpr); !ok || c.objectOf(sel.Sel) != types.Unsafe.Scope().Lookup("Pointer") { + return nil, false + } + return call.Args[0], true +} + +func typeName(t types.Type) string { + if iface, ok := t.(*types.Interface); ok && iface.Empty() { + return "interface{}" + } + return t.String() +} + +func isInterfaceType(t types.Type) bool { + _, ok := t.Underlying().(*types.Interface) + return ok +} + +func hasInterfaceType(c *cursor, expr dst.Expr) bool { + if ident, ok := expr.(*dst.Ident); ok && ident.Name == "_" { + return false + } + return isInterfaceType(c.typeOf(expr)) +} + +func location(c *cursor, n dst.Node) *spb.Location { + astNode := c.typesInfo.astMap[n] + if astNode == nil { + return nil + } + start := c.pkg.Fileset.Position(astNode.Pos()) + end := c.pkg.Fileset.Position(astNode.End()) + return &spb.Location{ + Package: c.pkg.TypePkg.Path(), + File: start.Filename, + Start: &spb.Position{ + Line: int64(start.Line), + Column: int64(start.Column), + }, + End: &spb.Position{ + Line: int64(end.Line), + Column: int64(end.Column), + }, + } +} + +func toRewriteLevel(lvl Level) spb.RewriteLevel { + switch lvl { + case None: + return spb.RewriteLevel_NONE + case Green: + return spb.RewriteLevel_GREEN + case Yellow: + return spb.RewriteLevel_YELLOW + case Red: + return spb.RewriteLevel_RED + default: + panic("unrecognized fix.Level %s" + lvl) + } +} + +func toTypeProto(typ types.Type) *spb.Type { + return statsutil.ShortAndLongNameFrom(typ.String()) +} + +func selectorStats(c *cursor, sel *dst.SelectorExpr, parent dst.Node) []*spb.Entry { + if _, ok := c.trackedProtoFieldSelector(sel); ok { + return []*spb.Entry{&spb.Entry{ + Location: location(c, sel), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(c.typeOf(sel.X)), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", sel), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_DIRECT_FIELD_ACCESS, + DirectFieldAccess: &spb.FieldAccess{ + FieldName: sel.Sel.Name, + FieldType: toTypeProto(c.typeOf(sel.Sel)), + }, + }, + }} + } + t := c.typeOfOrNil(sel.X) + if t == nil { + return logSiloed(c, nil, sel, parent) + } + if !c.shouldTrackType(t) || !strings.HasPrefix(sel.Sel.Name, "XXX_") { + return nil + } + return []*spb.Entry{&spb.Entry{ + Location: location(c, sel), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", sel), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_INTERNAL_FIELD_ACCESS, + InternalFieldAccess: &spb.FieldAccess{ + FieldName: sel.Sel.Name, + FieldType: toTypeProto(c.typeOf(sel.Sel)), + }, + }, + }} +} + +// oneofGetterOrNil determines whether call is like msg.GetFoo(), where foo is a +// oneof field of the message type of msg. It returns a corresponding stats +// entry if this is the case, and nil otherwise. +func oneofGetterOrNil(c *cursor, call *dst.CallExpr, parent dst.Node) *spb.Entry { + sel, sig, ok := c.protoFieldSelectorOrAccessor(call.Fun) + if !ok || sig == nil { + return nil + } + if !strings.HasPrefix(sel.Sel.Name, "Get") { + return nil + } + field := strings.TrimPrefix(sel.Sel.Name, "Get") + + t := c.typeOfOrNil(sel.X) + if t == nil { + return nil // skip over expression without type info (silo'ed?) + } + fullQual, isMsg := c.messageTypeName(t) + if !isMsg { + return nil + } + fullQual = strings.Trim(fullQual, `"`) + parts := strings.Split(fullQual, ".") + if len(parts) < 2 { + log.Errorf("not a fully qualified type name: %s", fullQual) + return nil + } + msg, pkg := parts[len(parts)-1], strings.Join(parts[:len(parts)-1], ".") + expectRetType := pkg + ".is" + msg + "_" + field + + res := sig.Results() + if res.Len() != 1 { + return nil + } + if res.At(0).Type().String() != expectRetType { + return nil + } + return &spb.Entry{ + Location: location(c, call), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", call), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_METHOD_CALL, + MethodCall: &spb.MethodCall{ + Method: sel.Sel.Name, + Type: spb.MethodCall_GET_ONEOF, + }, + }, + } +} + +// buildGetterEntryOrNil determines whether call is like msg.GetBuild() to get +// the value of the field called build of the proto message msg. It returns a +// corresponding stats Entry if this is the case, and nil otherwise. +func buildGetterEntryOrNil(c *cursor, call *dst.CallExpr, parent dst.Node) *spb.Entry { + sel, sig, ok := c.protoFieldSelectorOrAccessor(call.Fun) + if !ok || sig == nil { + return nil + } + if sel.Sel.Name != "GetBuild" { + return nil + } + t := c.typeOfOrNil(sel.X) + if t == nil { + return nil // skip over expression without type info (silo'ed?) + } + _, isMsg := c.messageTypeName(t) + if !isMsg { + return nil + } + return &spb.Entry{ + Location: location(c, call), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", call), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_METHOD_CALL, + MethodCall: &spb.MethodCall{ + Method: sel.Sel.Name, Type: spb.MethodCall_GET_BUILD, + }, + }, + } +} + +func callStats(c *cursor, call *dst.CallExpr, parent dst.Node) []*spb.Entry { + if c.isBuiltin(call.Fun) { + return nil + } + + var f types.Object + switch expr := call.Fun.(type) { + case *dst.Ident: + f = c.objectOf(expr) + case *dst.SelectorExpr: + f = c.objectOf(expr.Sel) + } + var fname, fpkg string + if f != nil { + fname = f.Name() + if p := f.Pkg(); p != nil { + fpkg = p.Path() + } + } else if _, ok := call.Fun.(*dst.InterfaceType); !ok { + // We could probably drop this branch. After AST transformations we may + // have no knowledge of the actual function object/type (e.g. if we + // don't maintain it correctly). It does not matter because none of the + // rewritten functions have interfaces as arguments, and it's safe to + // skip them. However, for explicit conversions, there's no + // corresponding function object/type. Addressing this todo requires + // updating the rewriter to correctly object identity for all introduced + // function and method calls. + return nil + } + + if entry := oneofGetterOrNil(c, call, parent); entry != nil { + return []*spb.Entry{entry} + } + if entry := buildGetterEntryOrNil(c, call, parent); entry != nil { + return []*spb.Entry{entry} + } + + if len(call.Args) == 0 { + return nil + } + + var out []*spb.Entry + + if len(call.Args) == 1 { + ft := c.typeOf(call.Fun) + // explicit conversion: interface{}(m) + if hasInterfaceType(c, call.Fun) { + // should we consider this a shallow copy too? + return logConversion(c, out, call.Args[0], nil, c.typeOf(call.Fun), call, parent, &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_EXPLICIT, + DestTypeName: ft.String(), + FuncArg: &spb.FuncArg{ + FunctionName: fname, + PackagePath: fpkg, + Signature: ft.String(), + }, + }, + }) + } + // explicit conversion: unsafe.Pointer(m) + if arg, ok := convToUnsafePointerArg(c, call); ok { + if t, ok := c.shouldLogCompositeType(c.typeOf(arg), true); ok { + return append(out, &spb.Entry{ + Location: location(c, call), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", call), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + DestTypeName: ft.String(), + Context: spb.Conversion_EXPLICIT, + }, + }, + }) + } + } + } + + // A function call. Look for arguments that are implicitly converted to an interface or shallow-copied. + // For example: + // "proto.Clone(m)" + // "f(*m)" + // "f(g())" where g() returns at least one protocol buffer message + + ft, ok := c.typeOf(call.Fun).(*types.Signature) + if !ok { + return out + } + + var argTypes []types.Type + arg0t := c.typeOfOrNil(call.Args[0]) + if arg0t == nil { + return logSiloed(c, out, call.Args[0], parent) + } + if tuple, ok := arg0t.(*types.Tuple); ok { + // Handle "f(g())"-style calls where g() returns a tuple of results. + for i := 0; i < tuple.Len(); i++ { + argTypes = append(argTypes, tuple.At(i).Type()) + } + } else { + // Handle "f(a,b,c)"-style calls. + for _, a := range call.Args { + t := c.typeOfOrNil(a) + if t == nil { + return logSiloed(c, out, a, parent) + } + argTypes = append(argTypes, t) + } + } + + for i, argType := range argTypes { + var t types.Type + if ft.Variadic() { + if i < ft.Params().Len()-1 { + t = ft.Params().At(i).Type() + } else { + t = ft.Params().At(ft.Params().Len() - 1).Type().(*types.Slice).Elem() + } + } else { + t = ft.Params().At(i).Type() + } + out = logConversion(c, out, nil, argType, t, call, parent, &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_CALL_ARGUMENT, + DestTypeName: typeName(t), + FuncArg: &spb.FuncArg{ + FunctionName: fname, + PackagePath: fpkg, + Signature: ft.String(), + }, + }, + }) + out = logShallowCopy(c, out, nil, argType, call, parent, &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_CALL_ARGUMENT, + }, + }) + } + + return out +} + +func assignStats(c *cursor, as *dst.AssignStmt, parent dst.Node) []*spb.Entry { + // a!=b && b==1 happens when right-hand side returns a tuple (e.g. map access or a function call) + if a, b := len(as.Lhs), len(as.Rhs); a != b && b != 1 { + panic(fmt.Sprintf("invalid assignment: lhs has %d exprs, rhs has %d exprs", a, b)) + } + + var out []*spb.Entry + for i, lhs := range as.Lhs { + // Ignore valid situations where a dst.Ident may have no type. For example: + // n (*dst.Ident) has no known type in + // switch n := in.(type) { + if _, ok := parent.(*dst.TypeSwitchStmt); ok && !c.hasType(lhs) { + continue + } + + lhst := c.typeOfOrNil(lhs) + if lhst == nil { + out = logSiloed(c, out, lhs, parent) + continue + } + conversion := &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_ASSIGNMENT, + DestTypeName: typeName(lhst), + }, + } + shallowCopy := &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_ASSIGN, + }, + } + if len(as.Lhs) == len(as.Rhs) { + out = logConversion(c, out, as.Rhs[i], nil, lhst, as, parent, conversion) + out = logShallowCopy(c, out, as.Rhs[i], nil, as, parent, shallowCopy) + } else { + rhst := c.typeOfOrNil(as.Rhs[0]) + if rhst == nil { + out = logSiloed(c, out, as.Rhs[0], parent) + continue + } + + if _, ok := rhst.(*types.Tuple); !ok { + continue + } + + out = logConversion(c, out, nil, rhst.(*types.Tuple).At(i).Type(), lhst, as, parent, conversion) + out = logShallowCopy(c, out, nil, rhst.(*types.Tuple).At(i).Type(), as, parent, shallowCopy) + } + } + return out +} + +func constructor(c *cursor, lit *dst.CompositeLit, parent dst.Node) *spb.Entry { + ct := &spb.Entry{ + Location: location(c, lit), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(c.typeOf(lit)), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", lit), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_CONSTRUCTOR, + Constructor: &spb.Constructor{}, + }, + } + ctype := c.typeOf(lit).String() + switch { + case strings.HasSuffix(ctype, "_builder"): + // Consider an empty builder as builder type, not empty. + ct.GetUse().GetConstructor().Type = spb.Constructor_BUILDER + case len(lit.Elts) == 0: + ct.GetUse().GetConstructor().Type = spb.Constructor_EMPTY_LITERAL + default: + ct.GetUse().GetConstructor().Type = spb.Constructor_NONEMPTY_LITERAL + } + return ct +} + +func compositeLitStats(c *cursor, lit *dst.CompositeLit, parent dst.Node) []*spb.Entry { + var out []*spb.Entry + + t := c.typeOfOrNil(lit) + if t == nil { + return logSiloed(c, out, lit, parent) + } + + // isBuilderType is cheaper. + if c.isBuilderType(t) || c.shouldTrackType(t) { + out = append(out, constructor(c, lit, parent)) + } + if len(lit.Elts) == 0 { + return out + } + + // Conversion in composite literal construction. +Elt: + for i, e := range lit.Elts { + var val dst.Expr + if kv, ok := e.(*dst.KeyValueExpr); ok { + val = kv.Value + } else { + val = e + } + use := func(t types.Type) *spb.Use { + return &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_COMPOSITE_LITERAL_ELEMENT, + DestTypeName: typeName(t), + }, + } + } + shallowCopyUse := &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT, + }, + } + switch t := c.underlyingTypeOf(lit).(type) { + case *types.Pointer: // e.g. []*struct{m *pb.M}{{m2}} + if kv, ok := e.(*dst.KeyValueExpr); ok { + t := c.typeOfOrNil(kv.Key) + if t == nil { + out = logSiloed(c, out, lit, parent) + continue Elt + } + out = logConversion(c, out, val, nil, t, e, lit, use(t)) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + } else { + if st, ok := t.Elem().Underlying().(*types.Struct); ok { + t := st.Field(i).Type() + out = logConversion(c, out, val, nil, t, e, lit, use(t)) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + } + } + case *types.Struct: // e.g. []struct{m *pb.M}{{m2}} + if kv, ok := e.(*dst.KeyValueExpr); ok { + t := c.typeOfOrNil(kv.Key) + if t == nil { + out = logSiloed(c, out, lit, parent) + continue Elt + } + out = logConversion(c, out, val, nil, t, e, lit, use(t)) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + } else { + t := t.Field(i).Type() + out = logConversion(c, out, val, nil, t, e, lit, use(t)) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + } + case *types.Slice: // e.g. []*pb.M2{m2} + out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem())) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + case *types.Array: // e.g. [1]*pb.M2{m2} + out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem())) + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) + case *types.Map: // e.g. map[*pb.M2]*pb.M2{m2: m2} + kv, ok := e.(*dst.KeyValueExpr) + if !ok { + ae := c.typesInfo.astMap[e] + panic("can't process a map element: not a key-value at " + + c.pkg.Fileset.Position(ae.Pos()).String()) + } + out = logConversion(c, out, kv.Key, nil, t.Key(), kv, lit, use(t.Key())) + out = logConversion(c, out, val, nil, t.Elem(), e, lit, use(t.Elem())) + out = logShallowCopy(c, out, kv.Key, nil, kv, lit, shallowCopyUse) // impossible? + out = logShallowCopy(c, out, val, nil, e, lit, shallowCopyUse) // map[Key]pb.M{Key{}: *m2} + case *types.Basic: + if t.Kind() == types.Invalid { + out = logSiloed(c, out, lit, parent) + continue Elt + } + default: + ae := c.typesInfo.astMap[e] + panic(fmt.Sprintf("unrecognized composite literal type %T (%v) at %s at level %s", + t, t, c.pkg.Fileset.Position(ae.Pos()).String(), c.lvl)) + } + } + + // Write to an internal field. + if t := c.typeOf(lit); c.shouldTrackType(t) { + var s *types.Struct + if p, ok := t.Underlying().(*types.Pointer); ok { + s = p.Elem().Underlying().(*types.Struct) + } else { + s = t.Underlying().(*types.Struct) + } + for i, e := range lit.Elts { + var fname string + var ftype types.Type + if kv, ok := e.(*dst.KeyValueExpr); ok { + f := kv.Key.(*dst.Ident) + fname, ftype = f.Name, c.typeOf(f) + } else { + f := s.Field(i) + fname, ftype = f.Name(), f.Type() + } + if !strings.HasPrefix(fname, "XXX_") { + continue + } + out = append(out, &spb.Entry{ + Location: location(c, e), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", e), + ParentType: fmt.Sprintf("%T", lit), + }, + Use: &spb.Use{ + Type: spb.Use_INTERNAL_FIELD_ACCESS, + InternalFieldAccess: &spb.FieldAccess{ + FieldName: fname, + FieldType: toTypeProto(ftype), + }, + }, + }) + } + } + + return out +} + +func sendStats(c *cursor, send *dst.SendStmt, parent dst.Node) []*spb.Entry { + underlying := c.underlyingTypeOfOrNil(send.Chan) + if underlying == nil { + return logSiloed(c, nil, send, parent) + } + dst := underlying.(*types.Chan).Elem() + out := logConversion(c, nil, send.Value, nil, dst, send, parent, &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_CHAN_SEND, + DestTypeName: typeName(dst), + }, + }) + out = append(out, logShallowCopy(c, nil, send.Value, nil, send, parent, &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_CHAN_SEND, + }, + })...) + return out +} + +func returnStats(c *cursor, ret *dst.ReturnStmt, parent dst.Node, parents map[dst.Node]dst.Node) []*spb.Entry { + p := parent + for i := 0; ; i++ { + if i > 1000 { + panic("too many parent nodes; is there a cycle in the parent structure?") + } + _, isfunc := p.(*dst.FuncDecl) + _, isflit := p.(*dst.FuncLit) + if isfunc || isflit { + break + } + p = parents[p] + } + var sig *types.Signature + switch p := p.(type) { + case *dst.FuncDecl: + pt := c.typeOfOrNil(p.Name) + if pt == nil { + return logSiloed(c, nil, ret, parent) + } + sig = pt.(*types.Signature) + case *dst.FuncLit: + pt := c.typeOfOrNil(p) + if pt == nil { + return logSiloed(c, nil, ret, parent) + } + sig = pt.(*types.Signature) + default: + panic(fmt.Sprintf("invalid parent function type %T; must be *dst.FuncDecl or *dst.FuncLit", p)) + } + + // Naked returns: there's no conversion possible because return values + // already have the same type as the function specifies. Any conversion + // to that type was already captured in assignments before the return + // statement. + if len(ret.Results) == 0 { + return nil + } + + // Handle the special case of returning the result of a function call as + // multiple results: + // func f() (a,b T) { + // return g() + // } + if len(ret.Results) == 1 && sig.Results().Len() > 1 { + rt0 := c.typeOfOrNil(ret.Results[0]) + if rt0 == nil { + return logSiloed(c, nil, ret.Results[0], parent) + } + rt := rt0.(*types.Tuple) + if a, b := sig.Results().Len(), rt.Len(); a != b { + panic(fmt.Sprintf("number of function return value types (%d) doesn't match the number of returned values as a tuple (%d)", a, b)) + } + var out []*spb.Entry + for i := 0; i < rt.Len(); i++ { + dst := sig.Results().At(i).Type() + out = logConversion(c, out, nil, rt.At(i).Type(), dst, ret, parent, &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_FUNC_RET, + DestTypeName: typeName(dst), + }, + }) + out = logShallowCopy(c, out, nil, rt.At(i).Type(), ret, parent, &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_FUNC_RET, + }, + }) + } + return out + } + + // Handle the typical case: the return statement has one value for each + // return value in the function signature. + if a, b := sig.Results().Len(), len(ret.Results); a != b { + panic(fmt.Sprintf("number of function return value types (%d) doesn't match the number of returned values (%d)", a, b)) + } + var out []*spb.Entry + for i, srcExpr := range ret.Results { + dst := sig.Results().At(i).Type() + out = logConversion(c, out, srcExpr, nil, dst, ret, parent, &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: &spb.Conversion{ + Context: spb.Conversion_FUNC_RET, + DestTypeName: typeName(dst), + }, + }) + out = logShallowCopy(c, out, srcExpr, nil, ret, parent, &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: &spb.ShallowCopy{ + Type: spb.ShallowCopy_FUNC_RET, + }, + }) + } + return out +} + +func typeAssertStats(c *cursor, ta *dst.TypeAssertExpr, parent dst.Node) []*spb.Entry { + // Ignore type assertions that don't have known types. This could + // happen, for example, in a type switch. The expression: + // in.(type) + // has no known type in: + // switch n := in.(type) { + if !c.hasType(ta) { + // Not handled: case with a proto type. + return nil + } + + t, ok := c.shouldLogCompositeType(c.typeOf(ta), true) + if !ok { + return nil + } + return []*spb.Entry{&spb.Entry{ + Location: location(c, ta), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", ta), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_TYPE_ASSERTION, + TypeAssertion: &spb.TypeAssertion{ + SrcType: toTypeProto(c.typeOf(ta.X)), + }, + }, + }} +} + +func typeSpecStats(c *cursor, ts *dst.TypeSpec, parent dst.Node) []*spb.Entry { + if ts.Assign { + // Type aliases are not interesting. + return nil + } + t := c.typeOf(ts.Type) + if !c.shouldTrackType(t) { + return nil + } + if p, ok := t.Underlying().(*types.Pointer); ok { + // For U in: + // type T *pb.M3 + // type U T + // we want t==*pb.M3, but for U in + // type U pb.M3 + // we want t==pb.M3 (not the underlying struct) + t = p + } + return []*spb.Entry{&spb.Entry{ + Location: location(c, ts), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", ts), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_TYPE_DEFINITION, + TypeDefinition: &spb.TypeDefinition{ + NewType: toTypeProto(c.typeOf(ts.Name)), + }, + }, + }} +} + +func structStats(c *cursor, st *dst.StructType, parent dst.Node) []*spb.Entry { + var idx int + var out []*spb.Entry + for _, f := range st.Fields.List { + idx += len(f.Names) + if len(f.Names) != 0 { + continue + } + t := c.typeOf(f.Type) + if !c.shouldTrackType(t) { + continue + } + out = append(out, &spb.Entry{ + Location: location(c, f), + Level: toRewriteLevel(c.lvl), + Type: toTypeProto(t), + Expr: &spb.Expression{ + Type: fmt.Sprintf("%T", st), + ParentType: fmt.Sprintf("%T", parent), + }, + Use: &spb.Use{ + Type: spb.Use_EMBEDDING, + Embedding: &spb.Embedding{ + FieldIndex: int64(idx), + }, + }, + }) + } + return out +} + +// isBuilderType returns true for types which we consider to represent builder types generated +// by the proto generator that builds protocol buffer messages. +func (c *cursor) isBuilderType(t types.Type) bool { + if p, ok := t.Underlying().(*types.Pointer); ok { + t = p.Elem() + } + nt, ok := t.(*types.Named) + if !ok { + return false + } + if !strings.HasSuffix(nt.String(), "_builder") { + return false + } + + // Check whether the type has a method called "Build" and it takes no argument + // and returns a proto. + for i := 0; i < nt.NumMethods(); i++ { + f := nt.Method(i) + if f.Name() != "Build" { + continue + } + sig, ok := f.Type().(*types.Signature) + if !ok { + return false + } + if sig.Params() != nil { + return false + } + res := sig.Results() + if res == nil || res.Len() != 1 { + return false + } + return c.shouldTrackType(res.At(0).Type()) + } + return false +} + +// shouldTrackType returns true for types which we consider to represent protocol buffers generated +// by the proto generator that the user requested to migrate. That is, types that should be +// considered for a rewrite during the open2opaque protocol buffer migration. +// +// The function returns true for all references to proto messages. Including +// accesses that we are currently not rewriting (e.g. via custom named types +// whose underlying type is a protocol buffer struct, or messages that +// explicitly stay on the OPEN_V1 API). +// +// Also see the shouldUpdateType function which returns true for types that we +// are currently rewriting. +func (c *cursor) shouldTrackType(t types.Type) bool { + name := strings.TrimPrefix(t.String(), "*") + + t = t.Underlying() + if p, ok := t.(*types.Pointer); ok { + t = p.Elem() + } + if !(protodetecttypes.Type{T: t}.IsMessage()) { + return false + } + name = strings.TrimPrefix(name, "*") + return len(c.typesToUpdate) == 0 || c.typesToUpdate[name] +}
diff --git a/internal/fix/stats_test.go b/internal/fix/stats_test.go new file mode 100644 index 0000000..90dbd97 --- /dev/null +++ b/internal/fix/stats_test.go
@@ -0,0 +1,879 @@ +// 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 fix + +import ( + "context" + "fmt" + "testing" + + "github.com/dave/dst" + "github.com/google/go-cmp/cmp" + spb "google.golang.org/open2opaque/internal/dashboard" + "google.golang.org/open2opaque/internal/o2o/statsutil" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestStats(t *testing.T) { + t.Setenv("GODEBUG", "gotypesalias=1") + + const extra = ` +type NotAProto struct { + S *string + Field struct{} +} +var notAProto *NotAProto +func g() string { return "" } +` + loc := func(startLn, startCol, endLn, endCol int64) *spb.Location { + return &spb.Location{ + Package: "google.golang.org/open2opaque/internal/fix/testdata/fake", + File: "google.golang.org/open2opaque/internal/fix/testdata/fake/pkg_test.go", + Start: &spb.Position{ + Line: startLn, + Column: startCol, + }, + End: &spb.Position{ + Line: endLn, + Column: endCol, + }, + } + } + const protoMsg = "google.golang.org/protobuf/proto.Message" + m2Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2") + m2 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2") + m3Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3") + m3 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3") + expr := func(node, parent any) *spb.Expression { + return &spb.Expression{ + Type: fmt.Sprintf("%T", node), + ParentType: fmt.Sprintf("%T", parent), + } + } + entry := func(loc *spb.Location, typ *spb.Type, expr *spb.Expression, use any) *spb.Entry { + e := &spb.Entry{ + Location: loc, + Level: toRewriteLevel(None), + Type: typ, + Expr: expr, + } + switch use := use.(type) { + case *spb.Use: + e.Use = use + case *spb.Constructor: + e.Use = &spb.Use{ + Type: spb.Use_CONSTRUCTOR, + Constructor: use, + } + case *spb.Conversion: + e.Use = &spb.Use{ + Type: spb.Use_CONVERSION, + Conversion: use, + } + case *spb.TypeAssertion: + e.Use = &spb.Use{ + Type: spb.Use_TYPE_ASSERTION, + TypeAssertion: use, + } + case *spb.TypeDefinition: + e.Use = &spb.Use{ + Type: spb.Use_TYPE_DEFINITION, + TypeDefinition: use, + } + case *spb.Embedding: + e.Use = &spb.Use{ + Type: spb.Use_EMBEDDING, + Embedding: use, + } + case *spb.ShallowCopy: + e.Use = &spb.Use{ + Type: spb.Use_SHALLOW_COPY, + ShallowCopy: use, + } + case *spb.MethodCall: + e.Use = spb.Use_builder{ + Type: spb.Use_METHOD_CALL, + MethodCall: use, + }.Build() + default: + panic(fmt.Sprintf("Bad 'use' type: %T", use)) + } + return e + } + directFieldAccess := func(fieldName, shortType, longType string) *spb.Use { + return &spb.Use{ + Type: spb.Use_DIRECT_FIELD_ACCESS, + DirectFieldAccess: &spb.FieldAccess{ + FieldName: fieldName, + FieldType: &spb.Type{ + ShortName: shortType, + LongName: longType, + }, + }, + } + } + typeMissing := func(loc *spb.Location, expr *spb.Expression) *spb.Entry { + return spb.Entry_builder{ + Status: spb.Status_builder{ + Type: spb.Status_FAIL, + Error: "type information missing; are dependencies in a silo?", + }.Build(), + Location: loc, + Level: toRewriteLevel(None), + Expr: expr, + }.Build() + } + + callStmt := expr(&dst.CallExpr{}, &dst.ExprStmt{}) + callCall := expr(&dst.CallExpr{}, &dst.CallExpr{}) + callConv := func(dstType, fname, fpkg, fsig string, c spb.Conversion_Context) *spb.Conversion { + return &spb.Conversion{ + DestTypeName: dstType, + FuncArg: &spb.FuncArg{ + FunctionName: fname, + PackagePath: fpkg, + Signature: fsig, + }, + Context: c, + } + } + retStmt := expr(&dst.ReturnStmt{}, &dst.BlockStmt{}) + + assignStmt := expr(&dst.AssignStmt{}, &dst.BlockStmt{}) + assignConv := func(dstType string) *spb.Conversion { + return &spb.Conversion{ + DestTypeName: dstType, + Context: spb.Conversion_ASSIGNMENT, + } + } + + kvCLit := expr(&dst.KeyValueExpr{}, &dst.CompositeLit{}) + starCLit := expr(&dst.StarExpr{}, &dst.CompositeLit{}) + identCLit := expr(&dst.Ident{}, &dst.CompositeLit{}) + elemConv := func(dstType string) *spb.Conversion { + return &spb.Conversion{ + DestTypeName: dstType, + Context: spb.Conversion_COMPOSITE_LITERAL_ELEMENT, + } + } + + selAssign := expr(&dst.SelectorExpr{}, &dst.AssignStmt{}) + + cLitUnary := expr(&dst.CompositeLit{}, &dst.UnaryExpr{}) + cLitCLit := expr(&dst.CompositeLit{}, &dst.CompositeLit{}) + emptyLiteral := &spb.Constructor{Type: spb.Constructor_EMPTY_LITERAL} + nonEmptyLiteral := &spb.Constructor{Type: spb.Constructor_NONEMPTY_LITERAL} + builderLiteral := &spb.Constructor{Type: spb.Constructor_BUILDER} + + tests := []test{{ + desc: "direct field accesses", + extra: extra, + in: ` +// simple proto2/proto3 access +_ = m2.S +m2.S = nil +_ = m3.S +m3.S = "" + +// repeated fields +m3.Ms[0] = nil +m3.Ms = append(m3.Ms, m3) + +// multiple selector expressions +m3.M.M.S = "" + +// accessing a method is not a direct field access +_ = m3.GetS + +// direct field access in non-protos don't count +var s NotAProto +_ = s.S +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + // simple proto2/proto3 access + entry(loc(3, 5, 3, 9), m2, selAssign, directFieldAccess("S", "*string", "*string")), + entry(loc(4, 1, 4, 5), m2, selAssign, directFieldAccess("S", "*string", "*string")), + entry(loc(5, 5, 5, 9), m3, selAssign, directFieldAccess("S", "string", "string")), + entry(loc(6, 1, 6, 5), m3, selAssign, directFieldAccess("S", "string", "string")), + // repeated fields + entry(loc(9, 1, 9, 6), m3, expr(&dst.SelectorExpr{}, &dst.IndexExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")), + entry(loc(10, 1, 10, 6), m3, selAssign, directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")), + entry(loc(10, 16, 10, 21), m3, expr(&dst.SelectorExpr{}, &dst.CallExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")), + // multiple selector expressions + entry(loc(13, 1, 13, 9), m3, selAssign, directFieldAccess("S", "string", "string")), + entry(loc(13, 1, 13, 7), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")), + entry(loc(13, 1, 13, 5), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")), + }, + }, + }, { + desc: "conversion in call expression", + extra: extra + ` +func retProto() *pb2.M2 { return nil } +func protoIn(*pb2.M2) { } +func protoIn2(*pb2.M2, *pb2.M2) { } +func efaceIn(interface{}) { } +func efaceIn2(a,b interface{}) { } +func efaceVararg(format string, args ...interface{}) { } +func msgVararg(format string, args ...proto.Message) { } + +type T struct{} +func (T) Method(interface{}) {} +`, + in: ` +protoIn(m2) // ignored: no conversion +protoIn(&pb2.M2{}) // ignored: no conversion + +efaceIn(notAProto) // ignored: not a proto + +efaceIn(m2) +efaceIn2(m2, &pb2.M2{}) + +proto.Clone(m2) +proto.Marshal(m2) + +proto.Clone(proto.Message(m2)) +proto.Marshal(proto.Message(m2)) + +efaceVararg("", m2, m2) +msgVararg("", m2) + +T{}.Method(m2) +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(3, 10, 3, 18), m2Val, cLitUnary, emptyLiteral), + entry(loc(7, 1, 7, 12), m2, callStmt, callConv("interface{}", "efaceIn", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(8, 15, 8, 23), m2Val, cLitUnary, emptyLiteral), + entry(loc(10, 1, 10, 16), m2, callStmt, callConv(protoMsg, "Clone", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") "+protoMsg, spb.Conversion_CALL_ARGUMENT)), + entry(loc(11, 1, 11, 18), m2, callStmt, callConv(protoMsg, "Marshal", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") ([]byte, error)", spb.Conversion_CALL_ARGUMENT)), + entry(loc(13, 13, 13, 30), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)), + entry(loc(14, 15, 14, 32), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)), + entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(17, 1, 17, 18), m2, callStmt, callConv(protoMsg, "msgVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ..."+protoMsg+")", spb.Conversion_CALL_ARGUMENT)), + entry(loc(19, 1, 19, 15), m2, callStmt, callConv("interface{}", "Method", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + }, + }, + }, { + desc: "conversion in assignment", + extra: ` +func multival() (interface{}, *pb2.M2, int, *pb2.M2) { + return nil, nil, 0, nil +} +`, + in: ` +x := m2 // ignored: no conversion +var mm *pb2.M2 = m2 // ignored: no conversion + +var in interface{} +in = m2 + +var min proto.Message +min = m3 + +var n int +n, in = 1, m2 +in, n2 := m2, 0 +in, in = m2, m3 + +var m map[string]*pb2.M2 +in, ok := m[""] +in, _ = m[""] + +in, in, n, in = multival() // eface->eface, *pb.M->eface, int->int, *pb.M->eface + +_, _, _, _, _, _, _ = mm, n, n2, ok, x, in, min +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(6, 1, 6, 8), m2, assignStmt, assignConv("interface{}")), // in = m2 + entry(loc(9, 1, 9, 9), m3, assignStmt, assignConv(protoMsg)), // min = m3 + entry(loc(12, 1, 12, 14), m2, assignStmt, assignConv("interface{}")), // n, in = 1, m2 + entry(loc(13, 1, 13, 16), m2, assignStmt, assignConv("interface{}")), // in, n2 := m2, 0 + entry(loc(14, 1, 14, 16), m2, assignStmt, assignConv("interface{}")), // in, in = m2, m3 + entry(loc(14, 1, 14, 16), m3, assignStmt, assignConv("interface{}")), // in, in = m2, m3 + entry(loc(17, 1, 17, 16), m2, assignStmt, assignConv("interface{}")), // in, ok := m[""] + entry(loc(18, 1, 18, 14), m2, assignStmt, assignConv("interface{}")), // in, _ = m[""] + entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : second arg + entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : last arg + }, + }, + }, { + desc: "conversion in construction", + in: ` +type t struct { + eface interface{} + msg proto.Message + m *pb2.M2 +} + +_ = t{ + eface: m2, + msg: m3, + m: m2, // ignore: no conversion +} + +_ = &t{ + eface: m2, + msg: m3, + m: m2, // ignore: no conversion +} + +_ = &t{m: m2} // ignore: no conversions +_ = &t{eface: m2} + +_ = []struct{m interface{}}{{m: m2}} +_ = []struct{m *pb2.M2}{{m: m2}} // ignore: no conversion + +_ = map[int]interface{}{0: m2} +_ = map[interface{}]int{m2: 0} +_ = map[interface{}]interface{}{m2: m2} + +_ = [...]interface{}{0:m2} +_ = []interface{}{0:m2} + +_ = &t{m2,m2,m2} // 2 findings + 1 ignored (no conversion) +_ = []interface{}{m2} +_ = []struct{m interface{}}{{m2}} + +_ = []*t{{m2, m2, m2}} +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(9, 3, 9, 12), m2, kvCLit, elemConv("interface{}")), + entry(loc(10, 3, 10, 10), m3, kvCLit, elemConv(protoMsg)), + entry(loc(15, 3, 15, 12), m2, kvCLit, elemConv("interface{}")), + entry(loc(16, 3, 16, 10), m3, kvCLit, elemConv(protoMsg)), + entry(loc(21, 8, 21, 17), m2, kvCLit, elemConv("interface{}")), + entry(loc(23, 30, 23, 35), m2, kvCLit, elemConv("interface{}")), + entry(loc(26, 25, 26, 30), m2, kvCLit, elemConv("interface{}")), + entry(loc(27, 25, 27, 30), m2, kvCLit, elemConv("interface{}")), + entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")), + entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")), + entry(loc(30, 22, 30, 26), m2, kvCLit, elemConv("interface{}")), + entry(loc(31, 19, 31, 23), m2, kvCLit, elemConv("interface{}")), + entry(loc(33, 8, 33, 10), m2, identCLit, elemConv("interface{}")), + entry(loc(33, 11, 33, 13), m2, identCLit, elemConv(protoMsg)), + entry(loc(34, 19, 34, 21), m2, identCLit, elemConv("interface{}")), + entry(loc(35, 30, 35, 32), m2, identCLit, elemConv("interface{}")), + entry(loc(37, 11, 37, 13), m2, identCLit, elemConv("interface{}")), + entry(loc(37, 15, 37, 17), m2, identCLit, elemConv(protoMsg)), + }, + }, + }, { + desc: "conversions to unsafe.Pointer", + extra: ` +func f(unsafe.Pointer) {} +func g(*pb2.M2) unsafe.Pointer{ return nil } +`, + in: ` +_ = unsafe.Pointer(m2) +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 5, 2, 23), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.Conversion{ + DestTypeName: "unsafe.Pointer", + Context: spb.Conversion_EXPLICIT, + }), + }, + }, + }, { + desc: "composite types: contexts", + extra: ` +type s struct {m *pb2.M2} +func f(interface{}) {} +func g() (int, uintptr, unsafe.Pointer) { + for { + return 0, 0, unsafe.Pointer(&pb2.M2{}) + } + return 0, 0, nil +} +`, + in: ` +// Various contexts: +f(&s{m: m2}) // conversion in call arg +var in interface{} +in = &s{m2} // conversion in assignment +in = struct{s *s}{s: &s{m2}} +_ = struct{s interface{}}{s: m2} +_ = struct{s interface{}}{m2} +f(unsafe.Pointer(&m2)) +in = <-make(chan *pb2.M2) // assignment from *pb.M to interface{} in assignment +make(chan interface{}) <- m2 +_ = func() interface{} { + if true { + return m2 + } + return nil +}() + +type namedChan chan interface{} +make(namedChan) <- m2 + +_ = in +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(3, 1, 3, 13), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(5, 1, 5, 12), m2, assignStmt, assignConv("interface{}")), + entry(loc(6, 1, 6, 29), m2, assignStmt, assignConv("interface{}")), + entry(loc(7, 27, 7, 32), m2, kvCLit, elemConv("interface{}")), + entry(loc(8, 27, 8, 29), m2, expr(&dst.Ident{}, &dst.CompositeLit{}), elemConv("interface{}")), + entry(loc(9, 3, 9, 22), m2, expr(&dst.CallExpr{}, &dst.CallExpr{}), &spb.Conversion{ + DestTypeName: "unsafe.Pointer", + Context: spb.Conversion_EXPLICIT, + }), + entry(loc(10, 1, 10, 26), m2, assignStmt, assignConv("interface{}")), + entry(loc(11, 1, 11, 29), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{ + DestTypeName: "interface{}", + Context: spb.Conversion_CHAN_SEND, + }), + entry(loc(14, 3, 14, 12), m2, retStmt, &spb.Conversion{ + DestTypeName: "interface{}", + Context: spb.Conversion_FUNC_RET, + }), + entry(loc(20, 1, 20, 22), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{ + DestTypeName: "interface{}", + Context: spb.Conversion_CHAN_SEND, + }), + // This is for function "g" which is defined in "extra" (outside "in") and hence the line number is out of range. + entry(loc(31, 16, 31, 41), m2, expr(&dst.CallExpr{}, &dst.ReturnStmt{}), &spb.Conversion{ + DestTypeName: "unsafe.Pointer", + Context: spb.Conversion_EXPLICIT, + }), + entry(loc(31, 32, 31, 40), m2Val, cLitUnary, emptyLiteral), + }, + }, + }, { + desc: "composite types: types", + extra: ` +func f(interface{}) {} +func g(_, _ interface{}) {} +`, + in: ` +f(&m2) +f([]*pb2.M2{}) +f([1]*pb2.M2{{}}) +f(map[int]*pb2.M2{}) +f(map[*pb2.M2]int{}) +g(func() (_,_ *pb2.M2) { return }()) // generates two entries +f(make(chan *pb2.M2)) // ignored: no proto value provided to reflection, only type +f(func(*pb2.M2){}) // ignored: no proto value provided to reflection, only type +f(func(a,b,c int, m *pb2.M2){}) // ignored: no proto value provided to reflection, only type + +type msg pb2.M2 +f(&msg{}) + +f(struct{m *pb2.M2}{m: m2}) + +_ = make(map[int]bool, len([]int{})) // make sure that builtins don't mess things up with variadic functions +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 1, 2, 7), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(3, 1, 3, 15), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(4, 1, 4, 18), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(4, 14, 4, 16), m2, cLitCLit, emptyLiteral), + entry(loc(5, 1, 5, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(6, 1, 6, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(12, 6, 12, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}), + entry(loc(13, 1, 13, 10), statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + entry(loc(13, 4, 13, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), cLitUnary, emptyLiteral), + entry(loc(15, 1, 15, 28), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + }, + }, + }, { + desc: "composite types: constructors", + in: ` +m2 = &pb2.M2{S: proto.String("s")} +_ = pb2.M2{ + S: proto.String("s"), + B: proto.Bool(true), +} +_ = pb2.M2_builder{ + S: proto.String("builder"), +}.Build() +m3s := []*pb3.M3{ + {S: "pointer"}, + {}, + pb3.M3_builder{}.Build(), +} +m3s = append(m3s, &pb3.M3{}) + +_ = []pb3.M3{ + {S: "shallow"}, + {}, +} + +type NotMsg struct{ M *pb2.M2 } +_ = &NotMsg{ M: &pb2.M2{} } +_ = NotMsg{} +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 7, 2, 35), m2Val, cLitUnary, nonEmptyLiteral), + entry(loc(3, 1, 6, 2), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(3, 5, 6, 2), m2Val, expr(&dst.CompositeLit{}, &dst.AssignStmt{}), nonEmptyLiteral), + entry(loc(7, 5, 9, 2), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral), + entry(loc(11, 2, 11, 16), m3, cLitCLit, nonEmptyLiteral), + entry(loc(12, 2, 12, 4), m3, cLitCLit, emptyLiteral), + entry(loc(13, 2, 13, 18), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral), + entry(loc(15, 20, 15, 28), m3Val, cLitUnary, emptyLiteral), + entry(loc(18, 2, 18, 16), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(19, 2, 19, 4), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(18, 2, 18, 16), m3Val, cLitCLit, nonEmptyLiteral), + entry(loc(19, 2, 19, 4), m3Val, cLitCLit, emptyLiteral), + entry(loc(23, 18, 23, 26), m2Val, cLitUnary, emptyLiteral), + }, + }, + }, { + desc: "type assertions", + extra: `type NotAProto struct { + S *string + Field struct{} +}`, + in: ` +var in interface{} +_ = in.(*pb3.M3) +_ = in.(*NotAProto) + +switch in.(type) {} +switch n := in.(type) { case int: _ = n } +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(3, 5, 3, 17), m3, expr(&dst.TypeAssertExpr{}, &dst.AssignStmt{}), &spb.TypeAssertion{ + SrcType: &spb.Type{ + ShortName: "interface{}", + LongName: "interface{}", + }, + }), + }, + }, + }, { + desc: "type defs", + extra: `type NotAProto struct { + S *string + Field struct{} +}`, + in: ` +type alias = *pb3.M3 // ignored +type notProto NotAProto // ignored +type myProto pb2.M2 +type myProtoPtr *pb3.M3 +type myProtoPtr2 myProtoPtr +type myProtoPtr3 *myProtoPtr // ignored: pointer to pointer + +// Composite types are not interesting to us. +type ignored1 struct { _ *pb2.M2 } +type ignored2 func(*pb3.M3) +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(4, 6, 4, 20), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProto")}), + entry(loc(5, 6, 5, 24), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr")}), + entry(loc(6, 6, 6, 28), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr2")}), + }, + }, + }, { + desc: "type embedding", + extra: `type NotAProto struct { + S *string + Field struct{} +}`, + in: ` +type ignored struct { + NotAProto + Named *pb3.M3 + _ *pb3.M3 +} + +type T struct { + n int + *pb3.M3 +} + +type U struct { + _, _ int + _ int + pb3.M3 +} + +type V struct { + _, _, _ int + T // ignored +} + +type W struct { + _, _, _ int + _ func(*pb3.M3) // ignored +} + +type named pb3.M3 +type X struct { + named +} +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(10, 2, 10, 9), m3, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 1}), + entry(loc(16, 2, 16, 8), m3Val, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 3}), + entry(loc(29, 6, 29, 18), m3Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named")}), + entry(loc(31, 2, 31, 7), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named"), expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 0}), + }, + }, + }, { + desc: "recursive type", + extra: "func f(interface{}){}", + in: ` +type S struct { + S *S + M *pb2.M2 +} +f(&S{}) +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(6, 1, 6, 8), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)), + }, + }, + }, { + desc: "mismatched number of return values", + extra: `func f() (*pb2.M2, *pb2.M2) { return nil, nil }`, + in: ` +_ = func() (m interface{}) { + m = &pb2.M2{} + return +} +_ = func() (a,b interface{}) { + return f() +} +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(3, 2, 3, 15), m2, assignStmt, assignConv("interface{}")), + entry(loc(3, 7, 3, 15), m2Val, cLitUnary, emptyLiteral), + entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{ + DestTypeName: "interface{}", + Context: spb.Conversion_FUNC_RET, + }), + entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{ + DestTypeName: "interface{}", + Context: spb.Conversion_FUNC_RET, + }), + }, + }, + }, { + // Copy a proto message by value. This test doesn't check for copying a + // composite type that results in a proto shallow copy). + desc: "direct shallow copies", + extra: ` +func args(int, pb2.M2) {} +func ret() (_ int, _ pb2.M2) { return } // naked return so that we don't trigger analysis +`, + in: ` +copy := *m2 // 0: assign-shallow-copy +copy = *m2 // 1: assign-shallow-copy +var n int +n, copy = 0, *m2 // 2: assign-shallow-copy +_,_ = copy,n // 3: assign-shallow-copy + +args(0, *m2) // 4: call-argument-shallow-copy + +args(ret()) // 5: call-argument-shallow-copy + +func() (int, pb2.M2) { + return 0, *m2 // 6: call-argument-return-shallow-copy +}() + +func() (int, pb2.M2) { + return ret() // 7: call-argument-return-shallow-copy +}() + +(*m2).GetS() // ignored: non-pointer receiver is fine + +m := map[string]pb2.M2{ + "": *m2, // 8: composite-literal-shallow-copy +} +copy, _ = m[""] // 9: assign-shallow-copy + +s := &struct { + m pb2.M2 +} { + m: *m2, // 11: composite-literal-shallow-copy +} +_ = s + +ch := make(chan pb2.M2) +ch <- *m2 // 12: chan-send-shallow-copy + +var in interface{} = *m2 +_ = in + +_ = []*struct{m pb2.M2}{ + {*m2}, // 14: composite-literal-shallow-copy + {m: *m2}, // 16: composite-literal-shallow-copy +} +_ = []struct{m pb2.M2}{ + {*m2}, // 17,18: composite-literal-shallow-copy of the entire struct '{*m2}' and of the message itself '*m2' + {m: *m2}, // 20,22: composite-literal-shallow-copy of the entire struct '{m: *m2}' and of the message itself '*m2' +} +_ = []pb2.M2{*m2} // 23: composite-literal-shallow-copy of the element '*m2' +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 1, 2, 12), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(3, 1, 3, 11), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(5, 1, 5, 17), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(6, 1, 6, 13), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(8, 1, 8, 13), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}), + entry(loc(10, 1, 10, 12), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}), + entry(loc(13, 2, 13, 15), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}), + entry(loc(17, 2, 17, 14), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}), + entry(loc(23, 2, 23, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(25, 1, 25, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(30, 2, 30, 8), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(35, 1, 35, 10), m2Val, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.ShallowCopy{Type: spb.ShallowCopy_CHAN_SEND}), + entry(loc(41, 3, 41, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(42, 3, 42, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(45, 2, 45, 7), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(46, 2, 46, 10), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(45, 3, 45, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(46, 3, 46, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + entry(loc(48, 14, 48, 17), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}), + }, + }, + }, { + // Copy a container with a proto struct. + desc: "indirect shallow copies", + extra: ` +type msg pb2.M2 +type S struct{m msg} +func args(_, _ S){} +func argsp(_, _ *S){}`, + in: ` +s := S{} // 0: shallow copy in definition +_ = s // 2: shallow copy in assignment +args(func() (_, _ S) { return }()) // 3,4: shallow via a tuple (twice because there are two values in the tuple) +_ = [1]pb2.M2{} // 5: copy an array + +// Those are OK because of the indirection +sp := &S{} +_ = sp +argsp(func() (_, _ *S) { return }()) +_ = [1]*pb2.M2{} + +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 1, 2, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(3, 1, 3, 6), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}), + entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}), + entry(loc(5, 1, 5, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}), + + // "type S struct{m msg}" definition: + entry(loc(17, 6, 17, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}), + }, + }, + }, { + desc: "type information missing", + in: ` +_ = siloedpb.Message{} +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + typeMissing(loc(2, 1, 2, 23), assignStmt), + typeMissing(loc(2, 1, 2, 23), assignStmt), + typeMissing(loc(2, 5, 2, 23), expr(&dst.CompositeLit{}, &dst.AssignStmt{})), + typeMissing(loc(2, 5, 2, 21), expr(&dst.SelectorExpr{}, &dst.CompositeLit{})), + typeMissing(loc(4, 2, 4, 27), assignStmt), + typeMissing(loc(4, 2, 4, 27), assignStmt), + }, + }, + }, { + // The method GetFoo for a oneof field foo only exists in the OPEN API. + desc: "GetOneof", + in: ` +switch x := m2.GetOneofField().(type) { + case *pb2.M2_StringOneof: + _ = x + default: +} +_ = m3.GetOneofField() +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 13, 2, 31), m2, expr(&dst.CallExpr{}, &dst.TypeAssertExpr{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}), + entry(loc(7, 5, 7, 23), m3, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}), + }, + }, + }, { + desc: "GetBuild", + in: ` +_ = m2.GetBuild() +`, + wantStats: map[Level][]*spb.Entry{ + None: []*spb.Entry{ + entry(loc(2, 5, 2, 18), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetBuild", Type: spb.MethodCall_GET_BUILD}), + }, + }, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + if tt.skip != "" { + t.Skip(tt.skip) + } + in := NewSrc(tt.in, tt.extra) + statsOnly := []Level{} + _, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, statsOnly) + if err != nil { + t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", tt.in, err, in) + } + for _, lvl := range []Level{None} { + want, ok := tt.wantStats[lvl] + if !ok { + continue + } + if len(want) != len(got[lvl]) { + t.Errorf("len(want)=%d != len(got[lvl])=%d", len(want), len(got[lvl])) + } + for idx := range want { + if diff := cmp.Diff(want[idx], got[lvl][idx], protocmp.Transform()); diff != "" { + // We don't print got/want because it's a lot of output and the diff is almost always enough. + t.Errorf("[%s] fixSources(level=%s, message %d) = diff:\n%s", tt.desc, lvl, idx, diff) + } + } + } + }) + } +} + +func TestSliceLiteral(t *testing.T) { + const src = ` +for _, _ = range map[*[]*pb2.M2]string{ + {}: "UNKNOWN_NOTIFICATION_TYPE", + { + { + S: nil, + }, + }: "UNKNOWN_NOTIFICATION_TYPE", +} { + panic("irrelevant") +} +` + in := NewSrc(src, "") + _, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, []Level{Green, Yellow, Red}) + if err != nil { + t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", src, err, in) + } + t.Logf("got: %v", got) +}
diff --git a/internal/fix/testdata/fake/fake.go b/internal/fix/testdata/fake/fake.go new file mode 100644 index 0000000..57ef425 --- /dev/null +++ b/internal/fix/testdata/fake/fake.go
@@ -0,0 +1,26 @@ +// 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 p + +import pb2 "google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto" +import pb3 "google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto" +import proto "google.golang.org/protobuf/proto" +import "unsafe" +import "context" + +var _ unsafe.Pointer +var _ = proto.String +var _ = context.Background + +func test_function() { + m2 := new(pb2.M2) + m2a := new(pb2.M2) + _, _ = m2, m2a + m3 := new(pb3.M3) + _ = m3 + _ = "TEST CODE STARTS HERE" + + _ = "TEST CODE ENDS HERE" +}
diff --git a/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go b/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go new file mode 100644 index 0000000..499c465 --- /dev/null +++ b/internal/fix/testdata/proto2test_go_proto/proto2test.pb.go
@@ -0,0 +1,3538 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: proto2test.proto + +package proto2test_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/gofeaturespb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type M2_Enum int32 + +const ( + M2_E_VAL M2_Enum = 0 +) + +// Enum value maps for M2_Enum. +var ( + M2_Enum_name = map[int32]string{ + 0: "E_VAL", + } + M2_Enum_value = map[string]int32{ + "E_VAL": 0, + } +) + +func (x M2_Enum) Enum() *M2_Enum { + p := new(M2_Enum) + *p = x + return p +} + +func (x M2_Enum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (M2_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_proto2test_proto_enumTypes[0].Descriptor() +} + +func (M2_Enum) Type() protoreflect.EnumType { + return &file_proto2test_proto_enumTypes[0] +} + +func (x M2_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use M2_Enum.Descriptor instead. +func (M2_Enum) EnumDescriptor() ([]byte, []int) { + return file_proto2test_proto_rawDescGZIP(), []int{1, 0} +} + +type OtherProto2_OtherEnum int32 + +const ( + OtherProto2_E_VAL OtherProto2_OtherEnum = 0 +) + +// Enum value maps for OtherProto2_OtherEnum. +var ( + OtherProto2_OtherEnum_name = map[int32]string{ + 0: "E_VAL", + } + OtherProto2_OtherEnum_value = map[string]int32{ + "E_VAL": 0, + } +) + +func (x OtherProto2_OtherEnum) Enum() *OtherProto2_OtherEnum { + p := new(OtherProto2_OtherEnum) + *p = x + return p +} + +func (x OtherProto2_OtherEnum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OtherProto2_OtherEnum) Descriptor() protoreflect.EnumDescriptor { + return file_proto2test_proto_enumTypes[1].Descriptor() +} + +func (OtherProto2_OtherEnum) Type() protoreflect.EnumType { + return &file_proto2test_proto_enumTypes[1] +} + +func (x OtherProto2_OtherEnum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use OtherProto2_OtherEnum.Descriptor instead. +func (OtherProto2_OtherEnum) EnumDescriptor() ([]byte, []int) { + return file_proto2test_proto_rawDescGZIP(), []int{2, 0} +} + +type DoNotMigrateMe struct { + state protoimpl.MessageState `protogen:"open.v1"` + B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DoNotMigrateMe) Reset() { + *x = DoNotMigrateMe{} + mi := &file_proto2test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DoNotMigrateMe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DoNotMigrateMe) ProtoMessage() {} + +func (x *DoNotMigrateMe) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DoNotMigrateMe.ProtoReflect.Descriptor instead. +func (*DoNotMigrateMe) Descriptor() ([]byte, []int) { + return file_proto2test_proto_rawDescGZIP(), []int{0} +} + +func (x *DoNotMigrateMe) GetB() bool { + if x != nil && x.B != nil { + return *x.B + } + return false +} + +type M2 struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes" json:"bytes,omitempty"` + F32 *float32 `protobuf:"fixed32,3,opt,name=f32" json:"f32,omitempty"` + F64 *float64 `protobuf:"fixed64,4,opt,name=f64" json:"f64,omitempty"` + I32 *int32 `protobuf:"varint,5,opt,name=i32" json:"i32,omitempty"` + I64 *int64 `protobuf:"varint,6,opt,name=i64" json:"i64,omitempty"` + Ui32 *uint32 `protobuf:"varint,7,opt,name=ui32" json:"ui32,omitempty"` + Ui64 *uint64 `protobuf:"varint,8,opt,name=ui64" json:"ui64,omitempty"` + S *string `protobuf:"bytes,9,opt,name=s" json:"s,omitempty"` + M *M2 `protobuf:"bytes,10,opt,name=m" json:"m,omitempty"` + Is []int32 `protobuf:"varint,11,rep,packed,name=is" json:"is,omitempty"` + Ms []*M2 `protobuf:"bytes,12,rep,name=ms" json:"ms,omitempty"` + Map map[string]bool `protobuf:"bytes,29,rep,name=map" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + E *M2_Enum `protobuf:"varint,13,opt,name=e,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum" json:"e,omitempty"` + // Types that are valid to be assigned to OneofField: + // + // *M2_StringOneof + // *M2_IntOneof + // *M2_MsgOneof + // *M2_EnumOneof + // *M2_BytesOneof + OneofField isM2_OneofField `protobuf_oneof:"oneof_field"` + // Types that are valid to be assigned to OneofField2: + // + // *M2_StringOneof2 + // *M2_IntOneof2 + // *M2_MsgOneof2 + // *M2_EnumOneof2 + // *M2_BytesOneof2 + OneofField2 isM2_OneofField2 `protobuf_oneof:"oneof_field2"` + Build *int32 `protobuf:"varint,24,opt,name=build" json:"build,omitempty"` + ProtoMessage_ *int32 `protobuf:"varint,25,opt,name=proto_message,json=protoMessage" json:"proto_message,omitempty"` + Reset_ *int32 `protobuf:"varint,26,opt,name=reset" json:"reset,omitempty"` + String_ *int32 `protobuf:"varint,27,opt,name=string" json:"string,omitempty"` + Descriptor_ *int32 `protobuf:"varint,28,opt,name=descriptor" json:"descriptor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *M2) Reset() { + *x = M2{} + mi := &file_proto2test_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *M2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*M2) ProtoMessage() {} + +func (x *M2) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *M2) GetB() bool { + if x != nil && x.B != nil { + return *x.B + } + return false +} + +func (x *M2) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +func (x *M2) GetF32() float32 { + if x != nil && x.F32 != nil { + return *x.F32 + } + return 0 +} + +func (x *M2) GetF64() float64 { + if x != nil && x.F64 != nil { + return *x.F64 + } + return 0 +} + +func (x *M2) GetI32() int32 { + if x != nil && x.I32 != nil { + return *x.I32 + } + return 0 +} + +func (x *M2) GetI64() int64 { + if x != nil && x.I64 != nil { + return *x.I64 + } + return 0 +} + +func (x *M2) GetUi32() uint32 { + if x != nil && x.Ui32 != nil { + return *x.Ui32 + } + return 0 +} + +func (x *M2) GetUi64() uint64 { + if x != nil && x.Ui64 != nil { + return *x.Ui64 + } + return 0 +} + +func (x *M2) GetS() string { + if x != nil && x.S != nil { + return *x.S + } + return "" +} + +func (x *M2) GetM() *M2 { + if x != nil { + return x.M + } + return nil +} + +func (x *M2) GetIs() []int32 { + if x != nil { + return x.Is + } + return nil +} + +func (x *M2) GetMs() []*M2 { + if x != nil { + return x.Ms + } + return nil +} + +func (x *M2) GetMap() map[string]bool { + if x != nil { + return x.Map + } + return nil +} + +func (x *M2) GetE() M2_Enum { + if x != nil && x.E != nil { + return *x.E + } + return M2_E_VAL +} + +func (x *M2) GetOneofField() isM2_OneofField { + if x != nil { + return x.OneofField + } + return nil +} + +func (x *M2) GetStringOneof() string { + if x != nil { + if x, ok := x.OneofField.(*M2_StringOneof); ok { + return x.StringOneof + } + } + return "" +} + +func (x *M2) GetIntOneof() int64 { + if x != nil { + if x, ok := x.OneofField.(*M2_IntOneof); ok { + return x.IntOneof + } + } + return 0 +} + +func (x *M2) GetMsgOneof() *M2 { + if x != nil { + if x, ok := x.OneofField.(*M2_MsgOneof); ok { + return x.MsgOneof + } + } + return nil +} + +func (x *M2) GetEnumOneof() M2_Enum { + if x != nil { + if x, ok := x.OneofField.(*M2_EnumOneof); ok { + return x.EnumOneof + } + } + return M2_E_VAL +} + +func (x *M2) GetBytesOneof() []byte { + if x != nil { + if x, ok := x.OneofField.(*M2_BytesOneof); ok { + return x.BytesOneof + } + } + return nil +} + +func (x *M2) GetOneofField2() isM2_OneofField2 { + if x != nil { + return x.OneofField2 + } + return nil +} + +func (x *M2) GetStringOneof2() string { + if x != nil { + if x, ok := x.OneofField2.(*M2_StringOneof2); ok { + return x.StringOneof2 + } + } + return "" +} + +func (x *M2) GetIntOneof2() int64 { + if x != nil { + if x, ok := x.OneofField2.(*M2_IntOneof2); ok { + return x.IntOneof2 + } + } + return 0 +} + +func (x *M2) GetMsgOneof2() *M2 { + if x != nil { + if x, ok := x.OneofField2.(*M2_MsgOneof2); ok { + return x.MsgOneof2 + } + } + return nil +} + +func (x *M2) GetEnumOneof2() M2_Enum { + if x != nil { + if x, ok := x.OneofField2.(*M2_EnumOneof2); ok { + return x.EnumOneof2 + } + } + return M2_E_VAL +} + +func (x *M2) GetBytesOneof2() []byte { + if x != nil { + if x, ok := x.OneofField2.(*M2_BytesOneof2); ok { + return x.BytesOneof2 + } + } + return nil +} + +func (x *M2) GetBuild_() int32 { + if x != nil && x.Build != nil { + return *x.Build + } + return 0 +} + +// Deprecated: Use GetBuild_ instead. +func (x *M2) GetBuild() int32 { + return x.GetBuild_() +} + +func (x *M2) GetProtoMessage() int32 { + if x != nil && x.ProtoMessage_ != nil { + return *x.ProtoMessage_ + } + return 0 +} + +// Deprecated: Use GetProtoMessage instead. +func (x *M2) GetProtoMessage_() int32 { + return x.GetProtoMessage() +} + +func (x *M2) GetReset() int32 { + if x != nil && x.Reset_ != nil { + return *x.Reset_ + } + return 0 +} + +// Deprecated: Use GetReset instead. +func (x *M2) GetReset_() int32 { + return x.GetReset() +} + +func (x *M2) GetString() int32 { + if x != nil && x.String_ != nil { + return *x.String_ + } + return 0 +} + +// Deprecated: Use GetString instead. +func (x *M2) GetString_() int32 { + return x.GetString() +} + +func (x *M2) GetDescriptor() int32 { + if x != nil && x.Descriptor_ != nil { + return *x.Descriptor_ + } + return 0 +} + +// Deprecated: Use GetDescriptor instead. +func (x *M2) GetDescriptor_() int32 { + return x.GetDescriptor() +} + +func (x *M2) SetB(v bool) { + x.B = &v +} + +func (x *M2) SetBytes(v []byte) { + if v == nil { + v = []byte{} + } + x.Bytes = v +} + +func (x *M2) SetF32(v float32) { + x.F32 = &v +} + +func (x *M2) SetF64(v float64) { + x.F64 = &v +} + +func (x *M2) SetI32(v int32) { + x.I32 = &v +} + +func (x *M2) SetI64(v int64) { + x.I64 = &v +} + +func (x *M2) SetUi32(v uint32) { + x.Ui32 = &v +} + +func (x *M2) SetUi64(v uint64) { + x.Ui64 = &v +} + +func (x *M2) SetS(v string) { + x.S = &v +} + +func (x *M2) SetM(v *M2) { + x.M = v +} + +func (x *M2) SetIs(v []int32) { + x.Is = v +} + +func (x *M2) SetMs(v []*M2) { + x.Ms = v +} + +func (x *M2) SetMap(v map[string]bool) { + x.Map = v +} + +func (x *M2) SetE(v M2_Enum) { + x.E = &v +} + +func (x *M2) SetStringOneof(v string) { + x.OneofField = &M2_StringOneof{v} +} + +func (x *M2) SetIntOneof(v int64) { + x.OneofField = &M2_IntOneof{v} +} + +func (x *M2) SetMsgOneof(v *M2) { + if v == nil { + x.OneofField = nil + return + } + x.OneofField = &M2_MsgOneof{v} +} + +func (x *M2) SetEnumOneof(v M2_Enum) { + x.OneofField = &M2_EnumOneof{v} +} + +func (x *M2) SetBytesOneof(v []byte) { + if v == nil { + v = []byte{} + } + x.OneofField = &M2_BytesOneof{v} +} + +func (x *M2) SetStringOneof2(v string) { + x.OneofField2 = &M2_StringOneof2{v} +} + +func (x *M2) SetIntOneof2(v int64) { + x.OneofField2 = &M2_IntOneof2{v} +} + +func (x *M2) SetMsgOneof2(v *M2) { + if v == nil { + x.OneofField2 = nil + return + } + x.OneofField2 = &M2_MsgOneof2{v} +} + +func (x *M2) SetEnumOneof2(v M2_Enum) { + x.OneofField2 = &M2_EnumOneof2{v} +} + +func (x *M2) SetBytesOneof2(v []byte) { + if v == nil { + v = []byte{} + } + x.OneofField2 = &M2_BytesOneof2{v} +} + +func (x *M2) SetBuild_(v int32) { + x.Build = &v +} + +func (x *M2) SetProtoMessage(v int32) { + x.ProtoMessage_ = &v +} + +func (x *M2) SetReset(v int32) { + x.Reset_ = &v +} + +func (x *M2) SetString(v int32) { + x.String_ = &v +} + +func (x *M2) SetDescriptor(v int32) { + x.Descriptor_ = &v +} + +func (x *M2) HasB() bool { + if x == nil { + return false + } + return x.B != nil +} + +func (x *M2) HasBytes() bool { + if x == nil { + return false + } + return x.Bytes != nil +} + +func (x *M2) HasF32() bool { + if x == nil { + return false + } + return x.F32 != nil +} + +func (x *M2) HasF64() bool { + if x == nil { + return false + } + return x.F64 != nil +} + +func (x *M2) HasI32() bool { + if x == nil { + return false + } + return x.I32 != nil +} + +func (x *M2) HasI64() bool { + if x == nil { + return false + } + return x.I64 != nil +} + +func (x *M2) HasUi32() bool { + if x == nil { + return false + } + return x.Ui32 != nil +} + +func (x *M2) HasUi64() bool { + if x == nil { + return false + } + return x.Ui64 != nil +} + +func (x *M2) HasS() bool { + if x == nil { + return false + } + return x.S != nil +} + +func (x *M2) HasM() bool { + if x == nil { + return false + } + return x.M != nil +} + +func (x *M2) HasE() bool { + if x == nil { + return false + } + return x.E != nil +} + +func (x *M2) HasOneofField() bool { + if x == nil { + return false + } + return x.OneofField != nil +} + +func (x *M2) HasStringOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M2_StringOneof) + return ok +} + +func (x *M2) HasIntOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M2_IntOneof) + return ok +} + +func (x *M2) HasMsgOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M2_MsgOneof) + return ok +} + +func (x *M2) HasEnumOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M2_EnumOneof) + return ok +} + +func (x *M2) HasBytesOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M2_BytesOneof) + return ok +} + +func (x *M2) HasOneofField2() bool { + if x == nil { + return false + } + return x.OneofField2 != nil +} + +func (x *M2) HasStringOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*M2_StringOneof2) + return ok +} + +func (x *M2) HasIntOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*M2_IntOneof2) + return ok +} + +func (x *M2) HasMsgOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*M2_MsgOneof2) + return ok +} + +func (x *M2) HasEnumOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*M2_EnumOneof2) + return ok +} + +func (x *M2) HasBytesOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*M2_BytesOneof2) + return ok +} + +func (x *M2) HasBuild_() bool { + if x == nil { + return false + } + return x.Build != nil +} + +func (x *M2) HasProtoMessage() bool { + if x == nil { + return false + } + return x.ProtoMessage_ != nil +} + +func (x *M2) HasReset() bool { + if x == nil { + return false + } + return x.Reset_ != nil +} + +func (x *M2) HasString() bool { + if x == nil { + return false + } + return x.String_ != nil +} + +func (x *M2) HasDescriptor() bool { + if x == nil { + return false + } + return x.Descriptor_ != nil +} + +func (x *M2) ClearB() { + x.B = nil +} + +func (x *M2) ClearBytes() { + x.Bytes = nil +} + +func (x *M2) ClearF32() { + x.F32 = nil +} + +func (x *M2) ClearF64() { + x.F64 = nil +} + +func (x *M2) ClearI32() { + x.I32 = nil +} + +func (x *M2) ClearI64() { + x.I64 = nil +} + +func (x *M2) ClearUi32() { + x.Ui32 = nil +} + +func (x *M2) ClearUi64() { + x.Ui64 = nil +} + +func (x *M2) ClearS() { + x.S = nil +} + +func (x *M2) ClearM() { + x.M = nil +} + +func (x *M2) ClearE() { + x.E = nil +} + +func (x *M2) ClearOneofField() { + x.OneofField = nil +} + +func (x *M2) ClearStringOneof() { + if _, ok := x.OneofField.(*M2_StringOneof); ok { + x.OneofField = nil + } +} + +func (x *M2) ClearIntOneof() { + if _, ok := x.OneofField.(*M2_IntOneof); ok { + x.OneofField = nil + } +} + +func (x *M2) ClearMsgOneof() { + if _, ok := x.OneofField.(*M2_MsgOneof); ok { + x.OneofField = nil + } +} + +func (x *M2) ClearEnumOneof() { + if _, ok := x.OneofField.(*M2_EnumOneof); ok { + x.OneofField = nil + } +} + +func (x *M2) ClearBytesOneof() { + if _, ok := x.OneofField.(*M2_BytesOneof); ok { + x.OneofField = nil + } +} + +func (x *M2) ClearOneofField2() { + x.OneofField2 = nil +} + +func (x *M2) ClearStringOneof2() { + if _, ok := x.OneofField2.(*M2_StringOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *M2) ClearIntOneof2() { + if _, ok := x.OneofField2.(*M2_IntOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *M2) ClearMsgOneof2() { + if _, ok := x.OneofField2.(*M2_MsgOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *M2) ClearEnumOneof2() { + if _, ok := x.OneofField2.(*M2_EnumOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *M2) ClearBytesOneof2() { + if _, ok := x.OneofField2.(*M2_BytesOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *M2) ClearBuild_() { + x.Build = nil +} + +func (x *M2) ClearProtoMessage() { + x.ProtoMessage_ = nil +} + +func (x *M2) ClearReset() { + x.Reset_ = nil +} + +func (x *M2) ClearString() { + x.String_ = nil +} + +func (x *M2) ClearDescriptor() { + x.Descriptor_ = nil +} + +const M2_OneofField_not_set_case case_M2_OneofField = 0 +const M2_StringOneof_case case_M2_OneofField = 14 +const M2_IntOneof_case case_M2_OneofField = 15 +const M2_MsgOneof_case case_M2_OneofField = 16 +const M2_EnumOneof_case case_M2_OneofField = 17 +const M2_BytesOneof_case case_M2_OneofField = 18 + +func (x *M2) WhichOneofField() case_M2_OneofField { + if x == nil { + return M2_OneofField_not_set_case + } + switch x.OneofField.(type) { + case *M2_StringOneof: + return M2_StringOneof_case + case *M2_IntOneof: + return M2_IntOneof_case + case *M2_MsgOneof: + return M2_MsgOneof_case + case *M2_EnumOneof: + return M2_EnumOneof_case + case *M2_BytesOneof: + return M2_BytesOneof_case + default: + return M2_OneofField_not_set_case + } +} + +const M2_OneofField2_not_set_case case_M2_OneofField2 = 0 +const M2_StringOneof2_case case_M2_OneofField2 = 19 +const M2_IntOneof2_case case_M2_OneofField2 = 20 +const M2_MsgOneof2_case case_M2_OneofField2 = 21 +const M2_EnumOneof2_case case_M2_OneofField2 = 22 +const M2_BytesOneof2_case case_M2_OneofField2 = 23 + +func (x *M2) WhichOneofField2() case_M2_OneofField2 { + if x == nil { + return M2_OneofField2_not_set_case + } + switch x.OneofField2.(type) { + case *M2_StringOneof2: + return M2_StringOneof2_case + case *M2_IntOneof2: + return M2_IntOneof2_case + case *M2_MsgOneof2: + return M2_MsgOneof2_case + case *M2_EnumOneof2: + return M2_EnumOneof2_case + case *M2_BytesOneof2: + return M2_BytesOneof2_case + default: + return M2_OneofField2_not_set_case + } +} + +type M2_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + B *bool + Bytes []byte + F32 *float32 + F64 *float64 + I32 *int32 + I64 *int64 + Ui32 *uint32 + Ui64 *uint64 + S *string + M *M2 + Is []int32 + Ms []*M2 + Map map[string]bool + E *M2_Enum + // Fields of oneof OneofField: + StringOneof *string + IntOneof *int64 + MsgOneof *M2 + EnumOneof *M2_Enum + BytesOneof []byte + // -- end of OneofField + // Fields of oneof OneofField2: + StringOneof2 *string + IntOneof2 *int64 + MsgOneof2 *M2 + EnumOneof2 *M2_Enum + BytesOneof2 []byte + // -- end of OneofField2 + Build_ *int32 + ProtoMessage *int32 + Reset *int32 + String *int32 + Descriptor *int32 +} + +func (b0 M2_builder) Build() *M2 { + m0 := &M2{} + b, x := &b0, m0 + _, _ = b, x + x.B = b.B + x.Bytes = b.Bytes + x.F32 = b.F32 + x.F64 = b.F64 + x.I32 = b.I32 + x.I64 = b.I64 + x.Ui32 = b.Ui32 + x.Ui64 = b.Ui64 + x.S = b.S + x.M = b.M + x.Is = b.Is + x.Ms = b.Ms + x.Map = b.Map + x.E = b.E + if b.StringOneof != nil { + x.OneofField = &M2_StringOneof{*b.StringOneof} + } + if b.IntOneof != nil { + x.OneofField = &M2_IntOneof{*b.IntOneof} + } + if b.MsgOneof != nil { + x.OneofField = &M2_MsgOneof{b.MsgOneof} + } + if b.EnumOneof != nil { + x.OneofField = &M2_EnumOneof{*b.EnumOneof} + } + if b.BytesOneof != nil { + x.OneofField = &M2_BytesOneof{b.BytesOneof} + } + if b.StringOneof2 != nil { + x.OneofField2 = &M2_StringOneof2{*b.StringOneof2} + } + if b.IntOneof2 != nil { + x.OneofField2 = &M2_IntOneof2{*b.IntOneof2} + } + if b.MsgOneof2 != nil { + x.OneofField2 = &M2_MsgOneof2{b.MsgOneof2} + } + if b.EnumOneof2 != nil { + x.OneofField2 = &M2_EnumOneof2{*b.EnumOneof2} + } + if b.BytesOneof2 != nil { + x.OneofField2 = &M2_BytesOneof2{b.BytesOneof2} + } + x.Build = b.Build_ + x.ProtoMessage_ = b.ProtoMessage + x.Reset_ = b.Reset + x.String_ = b.String + x.Descriptor_ = b.Descriptor + return m0 +} + +type case_M2_OneofField protoreflect.FieldNumber + +func (x case_M2_OneofField) String() string { + md := file_proto2test_proto_msgTypes[1].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type case_M2_OneofField2 protoreflect.FieldNumber + +func (x case_M2_OneofField2) String() string { + md := file_proto2test_proto_msgTypes[1].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isM2_OneofField interface { + isM2_OneofField() +} + +type M2_StringOneof struct { + StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,oneof"` +} + +type M2_IntOneof struct { + IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,oneof"` +} + +type M2_MsgOneof struct { + MsgOneof *M2 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,oneof"` +} + +type M2_EnumOneof struct { + EnumOneof M2_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum,oneof"` +} + +type M2_BytesOneof struct { + BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,oneof"` +} + +func (*M2_StringOneof) isM2_OneofField() {} + +func (*M2_IntOneof) isM2_OneofField() {} + +func (*M2_MsgOneof) isM2_OneofField() {} + +func (*M2_EnumOneof) isM2_OneofField() {} + +func (*M2_BytesOneof) isM2_OneofField() {} + +type isM2_OneofField2 interface { + isM2_OneofField2() +} + +type M2_StringOneof2 struct { + StringOneof2 string `protobuf:"bytes,19,opt,name=string_oneof2,json=stringOneof2,oneof"` +} + +type M2_IntOneof2 struct { + IntOneof2 int64 `protobuf:"varint,20,opt,name=int_oneof2,json=intOneof2,oneof"` +} + +type M2_MsgOneof2 struct { + MsgOneof2 *M2 `protobuf:"bytes,21,opt,name=msg_oneof2,json=msgOneof2,oneof"` +} + +type M2_EnumOneof2 struct { + EnumOneof2 M2_Enum `protobuf:"varint,22,opt,name=enum_oneof2,json=enumOneof2,enum=net.proto2.go.open2opaque.o2o.test.M2_Enum,oneof"` +} + +type M2_BytesOneof2 struct { + BytesOneof2 []byte `protobuf:"bytes,23,opt,name=bytes_oneof2,json=bytesOneof2,oneof"` +} + +func (*M2_StringOneof2) isM2_OneofField2() {} + +func (*M2_IntOneof2) isM2_OneofField2() {} + +func (*M2_MsgOneof2) isM2_OneofField2() {} + +func (*M2_EnumOneof2) isM2_OneofField2() {} + +func (*M2_BytesOneof2) isM2_OneofField2() {} + +type OtherProto2 struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + B *bool `protobuf:"varint,1,opt,name=b" json:"b,omitempty"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes" json:"bytes,omitempty"` + F32 *float32 `protobuf:"fixed32,3,opt,name=f32" json:"f32,omitempty"` + F64 *float64 `protobuf:"fixed64,4,opt,name=f64" json:"f64,omitempty"` + I32 *int32 `protobuf:"varint,5,opt,name=i32" json:"i32,omitempty"` + I64 *int64 `protobuf:"varint,6,opt,name=i64" json:"i64,omitempty"` + Ui32 *uint32 `protobuf:"varint,7,opt,name=ui32" json:"ui32,omitempty"` + Ui64 *uint64 `protobuf:"varint,8,opt,name=ui64" json:"ui64,omitempty"` + S *string `protobuf:"bytes,9,opt,name=s" json:"s,omitempty"` + M *OtherProto2 `protobuf:"bytes,10,opt,name=m" json:"m,omitempty"` + Is []int32 `protobuf:"varint,11,rep,packed,name=is" json:"is,omitempty"` + Ms []*OtherProto2 `protobuf:"bytes,12,rep,name=ms" json:"ms,omitempty"` + Map map[string]bool `protobuf:"bytes,29,rep,name=map" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + E *OtherProto2_OtherEnum `protobuf:"varint,13,opt,name=e,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum" json:"e,omitempty"` + // Types that are valid to be assigned to OneofField: + // + // *OtherProto2_StringOneof + // *OtherProto2_IntOneof + // *OtherProto2_MsgOneof + // *OtherProto2_EnumOneof + // *OtherProto2_BytesOneof + OneofField isOtherProto2_OneofField `protobuf_oneof:"oneof_field"` + // Types that are valid to be assigned to OneofField2: + // + // *OtherProto2_StringOneof2 + // *OtherProto2_IntOneof2 + // *OtherProto2_MsgOneof2 + // *OtherProto2_EnumOneof2 + // *OtherProto2_BytesOneof2 + OneofField2 isOtherProto2_OneofField2 `protobuf_oneof:"oneof_field2"` + Build *int32 `protobuf:"varint,24,opt,name=build" json:"build,omitempty"` + ProtoMessage_ *int32 `protobuf:"varint,25,opt,name=proto_message,json=protoMessage" json:"proto_message,omitempty"` + Reset_ *int32 `protobuf:"varint,26,opt,name=reset" json:"reset,omitempty"` + String_ *int32 `protobuf:"varint,27,opt,name=string" json:"string,omitempty"` + Descriptor_ *int32 `protobuf:"varint,28,opt,name=descriptor" json:"descriptor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OtherProto2) Reset() { + *x = OtherProto2{} + mi := &file_proto2test_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OtherProto2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtherProto2) ProtoMessage() {} + +func (x *OtherProto2) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *OtherProto2) GetB() bool { + if x != nil && x.B != nil { + return *x.B + } + return false +} + +func (x *OtherProto2) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +func (x *OtherProto2) GetF32() float32 { + if x != nil && x.F32 != nil { + return *x.F32 + } + return 0 +} + +func (x *OtherProto2) GetF64() float64 { + if x != nil && x.F64 != nil { + return *x.F64 + } + return 0 +} + +func (x *OtherProto2) GetI32() int32 { + if x != nil && x.I32 != nil { + return *x.I32 + } + return 0 +} + +func (x *OtherProto2) GetI64() int64 { + if x != nil && x.I64 != nil { + return *x.I64 + } + return 0 +} + +func (x *OtherProto2) GetUi32() uint32 { + if x != nil && x.Ui32 != nil { + return *x.Ui32 + } + return 0 +} + +func (x *OtherProto2) GetUi64() uint64 { + if x != nil && x.Ui64 != nil { + return *x.Ui64 + } + return 0 +} + +func (x *OtherProto2) GetS() string { + if x != nil && x.S != nil { + return *x.S + } + return "" +} + +func (x *OtherProto2) GetM() *OtherProto2 { + if x != nil { + return x.M + } + return nil +} + +func (x *OtherProto2) GetIs() []int32 { + if x != nil { + return x.Is + } + return nil +} + +func (x *OtherProto2) GetMs() []*OtherProto2 { + if x != nil { + return x.Ms + } + return nil +} + +func (x *OtherProto2) GetMap() map[string]bool { + if x != nil { + return x.Map + } + return nil +} + +func (x *OtherProto2) GetE() OtherProto2_OtherEnum { + if x != nil && x.E != nil { + return *x.E + } + return OtherProto2_E_VAL +} + +func (x *OtherProto2) GetOneofField() isOtherProto2_OneofField { + if x != nil { + return x.OneofField + } + return nil +} + +func (x *OtherProto2) GetStringOneof() string { + if x != nil { + if x, ok := x.OneofField.(*OtherProto2_StringOneof); ok { + return x.StringOneof + } + } + return "" +} + +func (x *OtherProto2) GetIntOneof() int64 { + if x != nil { + if x, ok := x.OneofField.(*OtherProto2_IntOneof); ok { + return x.IntOneof + } + } + return 0 +} + +func (x *OtherProto2) GetMsgOneof() *OtherProto2 { + if x != nil { + if x, ok := x.OneofField.(*OtherProto2_MsgOneof); ok { + return x.MsgOneof + } + } + return nil +} + +func (x *OtherProto2) GetEnumOneof() OtherProto2_OtherEnum { + if x != nil { + if x, ok := x.OneofField.(*OtherProto2_EnumOneof); ok { + return x.EnumOneof + } + } + return OtherProto2_E_VAL +} + +func (x *OtherProto2) GetBytesOneof() []byte { + if x != nil { + if x, ok := x.OneofField.(*OtherProto2_BytesOneof); ok { + return x.BytesOneof + } + } + return nil +} + +func (x *OtherProto2) GetOneofField2() isOtherProto2_OneofField2 { + if x != nil { + return x.OneofField2 + } + return nil +} + +func (x *OtherProto2) GetStringOneof2() string { + if x != nil { + if x, ok := x.OneofField2.(*OtherProto2_StringOneof2); ok { + return x.StringOneof2 + } + } + return "" +} + +func (x *OtherProto2) GetIntOneof2() int64 { + if x != nil { + if x, ok := x.OneofField2.(*OtherProto2_IntOneof2); ok { + return x.IntOneof2 + } + } + return 0 +} + +func (x *OtherProto2) GetMsgOneof2() *OtherProto2 { + if x != nil { + if x, ok := x.OneofField2.(*OtherProto2_MsgOneof2); ok { + return x.MsgOneof2 + } + } + return nil +} + +func (x *OtherProto2) GetEnumOneof2() OtherProto2_OtherEnum { + if x != nil { + if x, ok := x.OneofField2.(*OtherProto2_EnumOneof2); ok { + return x.EnumOneof2 + } + } + return OtherProto2_E_VAL +} + +func (x *OtherProto2) GetBytesOneof2() []byte { + if x != nil { + if x, ok := x.OneofField2.(*OtherProto2_BytesOneof2); ok { + return x.BytesOneof2 + } + } + return nil +} + +func (x *OtherProto2) GetBuild_() int32 { + if x != nil && x.Build != nil { + return *x.Build + } + return 0 +} + +// Deprecated: Use GetBuild_ instead. +func (x *OtherProto2) GetBuild() int32 { + return x.GetBuild_() +} + +func (x *OtherProto2) GetProtoMessage() int32 { + if x != nil && x.ProtoMessage_ != nil { + return *x.ProtoMessage_ + } + return 0 +} + +// Deprecated: Use GetProtoMessage instead. +func (x *OtherProto2) GetProtoMessage_() int32 { + return x.GetProtoMessage() +} + +func (x *OtherProto2) GetReset() int32 { + if x != nil && x.Reset_ != nil { + return *x.Reset_ + } + return 0 +} + +// Deprecated: Use GetReset instead. +func (x *OtherProto2) GetReset_() int32 { + return x.GetReset() +} + +func (x *OtherProto2) GetString() int32 { + if x != nil && x.String_ != nil { + return *x.String_ + } + return 0 +} + +// Deprecated: Use GetString instead. +func (x *OtherProto2) GetString_() int32 { + return x.GetString() +} + +func (x *OtherProto2) GetDescriptor() int32 { + if x != nil && x.Descriptor_ != nil { + return *x.Descriptor_ + } + return 0 +} + +// Deprecated: Use GetDescriptor instead. +func (x *OtherProto2) GetDescriptor_() int32 { + return x.GetDescriptor() +} + +func (x *OtherProto2) SetB(v bool) { + x.B = &v +} + +func (x *OtherProto2) SetBytes(v []byte) { + if v == nil { + v = []byte{} + } + x.Bytes = v +} + +func (x *OtherProto2) SetF32(v float32) { + x.F32 = &v +} + +func (x *OtherProto2) SetF64(v float64) { + x.F64 = &v +} + +func (x *OtherProto2) SetI32(v int32) { + x.I32 = &v +} + +func (x *OtherProto2) SetI64(v int64) { + x.I64 = &v +} + +func (x *OtherProto2) SetUi32(v uint32) { + x.Ui32 = &v +} + +func (x *OtherProto2) SetUi64(v uint64) { + x.Ui64 = &v +} + +func (x *OtherProto2) SetS(v string) { + x.S = &v +} + +func (x *OtherProto2) SetM(v *OtherProto2) { + x.M = v +} + +func (x *OtherProto2) SetIs(v []int32) { + x.Is = v +} + +func (x *OtherProto2) SetMs(v []*OtherProto2) { + x.Ms = v +} + +func (x *OtherProto2) SetMap(v map[string]bool) { + x.Map = v +} + +func (x *OtherProto2) SetE(v OtherProto2_OtherEnum) { + x.E = &v +} + +func (x *OtherProto2) SetStringOneof(v string) { + x.OneofField = &OtherProto2_StringOneof{v} +} + +func (x *OtherProto2) SetIntOneof(v int64) { + x.OneofField = &OtherProto2_IntOneof{v} +} + +func (x *OtherProto2) SetMsgOneof(v *OtherProto2) { + if v == nil { + x.OneofField = nil + return + } + x.OneofField = &OtherProto2_MsgOneof{v} +} + +func (x *OtherProto2) SetEnumOneof(v OtherProto2_OtherEnum) { + x.OneofField = &OtherProto2_EnumOneof{v} +} + +func (x *OtherProto2) SetBytesOneof(v []byte) { + if v == nil { + v = []byte{} + } + x.OneofField = &OtherProto2_BytesOneof{v} +} + +func (x *OtherProto2) SetStringOneof2(v string) { + x.OneofField2 = &OtherProto2_StringOneof2{v} +} + +func (x *OtherProto2) SetIntOneof2(v int64) { + x.OneofField2 = &OtherProto2_IntOneof2{v} +} + +func (x *OtherProto2) SetMsgOneof2(v *OtherProto2) { + if v == nil { + x.OneofField2 = nil + return + } + x.OneofField2 = &OtherProto2_MsgOneof2{v} +} + +func (x *OtherProto2) SetEnumOneof2(v OtherProto2_OtherEnum) { + x.OneofField2 = &OtherProto2_EnumOneof2{v} +} + +func (x *OtherProto2) SetBytesOneof2(v []byte) { + if v == nil { + v = []byte{} + } + x.OneofField2 = &OtherProto2_BytesOneof2{v} +} + +func (x *OtherProto2) SetBuild_(v int32) { + x.Build = &v +} + +func (x *OtherProto2) SetProtoMessage(v int32) { + x.ProtoMessage_ = &v +} + +func (x *OtherProto2) SetReset(v int32) { + x.Reset_ = &v +} + +func (x *OtherProto2) SetString(v int32) { + x.String_ = &v +} + +func (x *OtherProto2) SetDescriptor(v int32) { + x.Descriptor_ = &v +} + +func (x *OtherProto2) HasB() bool { + if x == nil { + return false + } + return x.B != nil +} + +func (x *OtherProto2) HasBytes() bool { + if x == nil { + return false + } + return x.Bytes != nil +} + +func (x *OtherProto2) HasF32() bool { + if x == nil { + return false + } + return x.F32 != nil +} + +func (x *OtherProto2) HasF64() bool { + if x == nil { + return false + } + return x.F64 != nil +} + +func (x *OtherProto2) HasI32() bool { + if x == nil { + return false + } + return x.I32 != nil +} + +func (x *OtherProto2) HasI64() bool { + if x == nil { + return false + } + return x.I64 != nil +} + +func (x *OtherProto2) HasUi32() bool { + if x == nil { + return false + } + return x.Ui32 != nil +} + +func (x *OtherProto2) HasUi64() bool { + if x == nil { + return false + } + return x.Ui64 != nil +} + +func (x *OtherProto2) HasS() bool { + if x == nil { + return false + } + return x.S != nil +} + +func (x *OtherProto2) HasM() bool { + if x == nil { + return false + } + return x.M != nil +} + +func (x *OtherProto2) HasE() bool { + if x == nil { + return false + } + return x.E != nil +} + +func (x *OtherProto2) HasOneofField() bool { + if x == nil { + return false + } + return x.OneofField != nil +} + +func (x *OtherProto2) HasStringOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*OtherProto2_StringOneof) + return ok +} + +func (x *OtherProto2) HasIntOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*OtherProto2_IntOneof) + return ok +} + +func (x *OtherProto2) HasMsgOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*OtherProto2_MsgOneof) + return ok +} + +func (x *OtherProto2) HasEnumOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*OtherProto2_EnumOneof) + return ok +} + +func (x *OtherProto2) HasBytesOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*OtherProto2_BytesOneof) + return ok +} + +func (x *OtherProto2) HasOneofField2() bool { + if x == nil { + return false + } + return x.OneofField2 != nil +} + +func (x *OtherProto2) HasStringOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*OtherProto2_StringOneof2) + return ok +} + +func (x *OtherProto2) HasIntOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*OtherProto2_IntOneof2) + return ok +} + +func (x *OtherProto2) HasMsgOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*OtherProto2_MsgOneof2) + return ok +} + +func (x *OtherProto2) HasEnumOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*OtherProto2_EnumOneof2) + return ok +} + +func (x *OtherProto2) HasBytesOneof2() bool { + if x == nil { + return false + } + _, ok := x.OneofField2.(*OtherProto2_BytesOneof2) + return ok +} + +func (x *OtherProto2) HasBuild_() bool { + if x == nil { + return false + } + return x.Build != nil +} + +func (x *OtherProto2) HasProtoMessage() bool { + if x == nil { + return false + } + return x.ProtoMessage_ != nil +} + +func (x *OtherProto2) HasReset() bool { + if x == nil { + return false + } + return x.Reset_ != nil +} + +func (x *OtherProto2) HasString() bool { + if x == nil { + return false + } + return x.String_ != nil +} + +func (x *OtherProto2) HasDescriptor() bool { + if x == nil { + return false + } + return x.Descriptor_ != nil +} + +func (x *OtherProto2) ClearB() { + x.B = nil +} + +func (x *OtherProto2) ClearBytes() { + x.Bytes = nil +} + +func (x *OtherProto2) ClearF32() { + x.F32 = nil +} + +func (x *OtherProto2) ClearF64() { + x.F64 = nil +} + +func (x *OtherProto2) ClearI32() { + x.I32 = nil +} + +func (x *OtherProto2) ClearI64() { + x.I64 = nil +} + +func (x *OtherProto2) ClearUi32() { + x.Ui32 = nil +} + +func (x *OtherProto2) ClearUi64() { + x.Ui64 = nil +} + +func (x *OtherProto2) ClearS() { + x.S = nil +} + +func (x *OtherProto2) ClearM() { + x.M = nil +} + +func (x *OtherProto2) ClearE() { + x.E = nil +} + +func (x *OtherProto2) ClearOneofField() { + x.OneofField = nil +} + +func (x *OtherProto2) ClearStringOneof() { + if _, ok := x.OneofField.(*OtherProto2_StringOneof); ok { + x.OneofField = nil + } +} + +func (x *OtherProto2) ClearIntOneof() { + if _, ok := x.OneofField.(*OtherProto2_IntOneof); ok { + x.OneofField = nil + } +} + +func (x *OtherProto2) ClearMsgOneof() { + if _, ok := x.OneofField.(*OtherProto2_MsgOneof); ok { + x.OneofField = nil + } +} + +func (x *OtherProto2) ClearEnumOneof() { + if _, ok := x.OneofField.(*OtherProto2_EnumOneof); ok { + x.OneofField = nil + } +} + +func (x *OtherProto2) ClearBytesOneof() { + if _, ok := x.OneofField.(*OtherProto2_BytesOneof); ok { + x.OneofField = nil + } +} + +func (x *OtherProto2) ClearOneofField2() { + x.OneofField2 = nil +} + +func (x *OtherProto2) ClearStringOneof2() { + if _, ok := x.OneofField2.(*OtherProto2_StringOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *OtherProto2) ClearIntOneof2() { + if _, ok := x.OneofField2.(*OtherProto2_IntOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *OtherProto2) ClearMsgOneof2() { + if _, ok := x.OneofField2.(*OtherProto2_MsgOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *OtherProto2) ClearEnumOneof2() { + if _, ok := x.OneofField2.(*OtherProto2_EnumOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *OtherProto2) ClearBytesOneof2() { + if _, ok := x.OneofField2.(*OtherProto2_BytesOneof2); ok { + x.OneofField2 = nil + } +} + +func (x *OtherProto2) ClearBuild_() { + x.Build = nil +} + +func (x *OtherProto2) ClearProtoMessage() { + x.ProtoMessage_ = nil +} + +func (x *OtherProto2) ClearReset() { + x.Reset_ = nil +} + +func (x *OtherProto2) ClearString() { + x.String_ = nil +} + +func (x *OtherProto2) ClearDescriptor() { + x.Descriptor_ = nil +} + +const OtherProto2_OneofField_not_set_case case_OtherProto2_OneofField = 0 +const OtherProto2_StringOneof_case case_OtherProto2_OneofField = 14 +const OtherProto2_IntOneof_case case_OtherProto2_OneofField = 15 +const OtherProto2_MsgOneof_case case_OtherProto2_OneofField = 16 +const OtherProto2_EnumOneof_case case_OtherProto2_OneofField = 17 +const OtherProto2_BytesOneof_case case_OtherProto2_OneofField = 18 + +func (x *OtherProto2) WhichOneofField() case_OtherProto2_OneofField { + if x == nil { + return OtherProto2_OneofField_not_set_case + } + switch x.OneofField.(type) { + case *OtherProto2_StringOneof: + return OtherProto2_StringOneof_case + case *OtherProto2_IntOneof: + return OtherProto2_IntOneof_case + case *OtherProto2_MsgOneof: + return OtherProto2_MsgOneof_case + case *OtherProto2_EnumOneof: + return OtherProto2_EnumOneof_case + case *OtherProto2_BytesOneof: + return OtherProto2_BytesOneof_case + default: + return OtherProto2_OneofField_not_set_case + } +} + +const OtherProto2_OneofField2_not_set_case case_OtherProto2_OneofField2 = 0 +const OtherProto2_StringOneof2_case case_OtherProto2_OneofField2 = 19 +const OtherProto2_IntOneof2_case case_OtherProto2_OneofField2 = 20 +const OtherProto2_MsgOneof2_case case_OtherProto2_OneofField2 = 21 +const OtherProto2_EnumOneof2_case case_OtherProto2_OneofField2 = 22 +const OtherProto2_BytesOneof2_case case_OtherProto2_OneofField2 = 23 + +func (x *OtherProto2) WhichOneofField2() case_OtherProto2_OneofField2 { + if x == nil { + return OtherProto2_OneofField2_not_set_case + } + switch x.OneofField2.(type) { + case *OtherProto2_StringOneof2: + return OtherProto2_StringOneof2_case + case *OtherProto2_IntOneof2: + return OtherProto2_IntOneof2_case + case *OtherProto2_MsgOneof2: + return OtherProto2_MsgOneof2_case + case *OtherProto2_EnumOneof2: + return OtherProto2_EnumOneof2_case + case *OtherProto2_BytesOneof2: + return OtherProto2_BytesOneof2_case + default: + return OtherProto2_OneofField2_not_set_case + } +} + +type OtherProto2_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + B *bool + Bytes []byte + F32 *float32 + F64 *float64 + I32 *int32 + I64 *int64 + Ui32 *uint32 + Ui64 *uint64 + S *string + M *OtherProto2 + Is []int32 + Ms []*OtherProto2 + Map map[string]bool + E *OtherProto2_OtherEnum + // Fields of oneof OneofField: + StringOneof *string + IntOneof *int64 + MsgOneof *OtherProto2 + EnumOneof *OtherProto2_OtherEnum + BytesOneof []byte + // -- end of OneofField + // Fields of oneof OneofField2: + StringOneof2 *string + IntOneof2 *int64 + MsgOneof2 *OtherProto2 + EnumOneof2 *OtherProto2_OtherEnum + BytesOneof2 []byte + // -- end of OneofField2 + Build_ *int32 + ProtoMessage *int32 + Reset *int32 + String *int32 + Descriptor *int32 +} + +func (b0 OtherProto2_builder) Build() *OtherProto2 { + m0 := &OtherProto2{} + b, x := &b0, m0 + _, _ = b, x + x.B = b.B + x.Bytes = b.Bytes + x.F32 = b.F32 + x.F64 = b.F64 + x.I32 = b.I32 + x.I64 = b.I64 + x.Ui32 = b.Ui32 + x.Ui64 = b.Ui64 + x.S = b.S + x.M = b.M + x.Is = b.Is + x.Ms = b.Ms + x.Map = b.Map + x.E = b.E + if b.StringOneof != nil { + x.OneofField = &OtherProto2_StringOneof{*b.StringOneof} + } + if b.IntOneof != nil { + x.OneofField = &OtherProto2_IntOneof{*b.IntOneof} + } + if b.MsgOneof != nil { + x.OneofField = &OtherProto2_MsgOneof{b.MsgOneof} + } + if b.EnumOneof != nil { + x.OneofField = &OtherProto2_EnumOneof{*b.EnumOneof} + } + if b.BytesOneof != nil { + x.OneofField = &OtherProto2_BytesOneof{b.BytesOneof} + } + if b.StringOneof2 != nil { + x.OneofField2 = &OtherProto2_StringOneof2{*b.StringOneof2} + } + if b.IntOneof2 != nil { + x.OneofField2 = &OtherProto2_IntOneof2{*b.IntOneof2} + } + if b.MsgOneof2 != nil { + x.OneofField2 = &OtherProto2_MsgOneof2{b.MsgOneof2} + } + if b.EnumOneof2 != nil { + x.OneofField2 = &OtherProto2_EnumOneof2{*b.EnumOneof2} + } + if b.BytesOneof2 != nil { + x.OneofField2 = &OtherProto2_BytesOneof2{b.BytesOneof2} + } + x.Build = b.Build_ + x.ProtoMessage_ = b.ProtoMessage + x.Reset_ = b.Reset + x.String_ = b.String + x.Descriptor_ = b.Descriptor + return m0 +} + +type case_OtherProto2_OneofField protoreflect.FieldNumber + +func (x case_OtherProto2_OneofField) String() string { + md := file_proto2test_proto_msgTypes[2].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type case_OtherProto2_OneofField2 protoreflect.FieldNumber + +func (x case_OtherProto2_OneofField2) String() string { + md := file_proto2test_proto_msgTypes[2].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isOtherProto2_OneofField interface { + isOtherProto2_OneofField() +} + +type OtherProto2_StringOneof struct { + StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,oneof"` +} + +type OtherProto2_IntOneof struct { + IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,oneof"` +} + +type OtherProto2_MsgOneof struct { + MsgOneof *OtherProto2 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,oneof"` +} + +type OtherProto2_EnumOneof struct { + EnumOneof OtherProto2_OtherEnum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum,oneof"` +} + +type OtherProto2_BytesOneof struct { + BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,oneof"` +} + +func (*OtherProto2_StringOneof) isOtherProto2_OneofField() {} + +func (*OtherProto2_IntOneof) isOtherProto2_OneofField() {} + +func (*OtherProto2_MsgOneof) isOtherProto2_OneofField() {} + +func (*OtherProto2_EnumOneof) isOtherProto2_OneofField() {} + +func (*OtherProto2_BytesOneof) isOtherProto2_OneofField() {} + +type isOtherProto2_OneofField2 interface { + isOtherProto2_OneofField2() +} + +type OtherProto2_StringOneof2 struct { + StringOneof2 string `protobuf:"bytes,19,opt,name=string_oneof2,json=stringOneof2,oneof"` +} + +type OtherProto2_IntOneof2 struct { + IntOneof2 int64 `protobuf:"varint,20,opt,name=int_oneof2,json=intOneof2,oneof"` +} + +type OtherProto2_MsgOneof2 struct { + MsgOneof2 *OtherProto2 `protobuf:"bytes,21,opt,name=msg_oneof2,json=msgOneof2,oneof"` +} + +type OtherProto2_EnumOneof2 struct { + EnumOneof2 OtherProto2_OtherEnum `protobuf:"varint,22,opt,name=enum_oneof2,json=enumOneof2,enum=net.proto2.go.open2opaque.o2o.test.OtherProto2_OtherEnum,oneof"` +} + +type OtherProto2_BytesOneof2 struct { + BytesOneof2 []byte `protobuf:"bytes,23,opt,name=bytes_oneof2,json=bytesOneof2,oneof"` +} + +func (*OtherProto2_StringOneof2) isOtherProto2_OneofField2() {} + +func (*OtherProto2_IntOneof2) isOtherProto2_OneofField2() {} + +func (*OtherProto2_MsgOneof2) isOtherProto2_OneofField2() {} + +func (*OtherProto2_EnumOneof2) isOtherProto2_OneofField2() {} + +func (*OtherProto2_BytesOneof2) isOtherProto2_OneofField2() {} + +type M2Outer struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Types that are valid to be assigned to OuterOneof: + // + // *M2Outer_InnerMsg + // *M2Outer_StringOneof + OuterOneof isM2Outer_OuterOneof `protobuf_oneof:"outer_oneof"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *M2Outer) Reset() { + *x = M2Outer{} + mi := &file_proto2test_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *M2Outer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*M2Outer) ProtoMessage() {} + +func (x *M2Outer) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *M2Outer) GetOuterOneof() isM2Outer_OuterOneof { + if x != nil { + return x.OuterOneof + } + return nil +} + +func (x *M2Outer) GetInnerMsg() *M2Outer_MInner { + if x != nil { + if x, ok := x.OuterOneof.(*M2Outer_InnerMsg); ok { + return x.InnerMsg + } + } + return nil +} + +func (x *M2Outer) GetStringOneof() string { + if x != nil { + if x, ok := x.OuterOneof.(*M2Outer_StringOneof); ok { + return x.StringOneof + } + } + return "" +} + +func (x *M2Outer) SetInnerMsg(v *M2Outer_MInner) { + if v == nil { + x.OuterOneof = nil + return + } + x.OuterOneof = &M2Outer_InnerMsg{v} +} + +func (x *M2Outer) SetStringOneof(v string) { + x.OuterOneof = &M2Outer_StringOneof{v} +} + +func (x *M2Outer) HasOuterOneof() bool { + if x == nil { + return false + } + return x.OuterOneof != nil +} + +func (x *M2Outer) HasInnerMsg() bool { + if x == nil { + return false + } + _, ok := x.OuterOneof.(*M2Outer_InnerMsg) + return ok +} + +func (x *M2Outer) HasStringOneof() bool { + if x == nil { + return false + } + _, ok := x.OuterOneof.(*M2Outer_StringOneof) + return ok +} + +func (x *M2Outer) ClearOuterOneof() { + x.OuterOneof = nil +} + +func (x *M2Outer) ClearInnerMsg() { + if _, ok := x.OuterOneof.(*M2Outer_InnerMsg); ok { + x.OuterOneof = nil + } +} + +func (x *M2Outer) ClearStringOneof() { + if _, ok := x.OuterOneof.(*M2Outer_StringOneof); ok { + x.OuterOneof = nil + } +} + +const M2Outer_OuterOneof_not_set_case case_M2Outer_OuterOneof = 0 +const M2Outer_InnerMsg_case case_M2Outer_OuterOneof = 1 +const M2Outer_StringOneof_case case_M2Outer_OuterOneof = 2 + +func (x *M2Outer) WhichOuterOneof() case_M2Outer_OuterOneof { + if x == nil { + return M2Outer_OuterOneof_not_set_case + } + switch x.OuterOneof.(type) { + case *M2Outer_InnerMsg: + return M2Outer_InnerMsg_case + case *M2Outer_StringOneof: + return M2Outer_StringOneof_case + default: + return M2Outer_OuterOneof_not_set_case + } +} + +type M2Outer_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof OuterOneof: + InnerMsg *M2Outer_MInner + StringOneof *string + // -- end of OuterOneof +} + +func (b0 M2Outer_builder) Build() *M2Outer { + m0 := &M2Outer{} + b, x := &b0, m0 + _, _ = b, x + if b.InnerMsg != nil { + x.OuterOneof = &M2Outer_InnerMsg{b.InnerMsg} + } + if b.StringOneof != nil { + x.OuterOneof = &M2Outer_StringOneof{*b.StringOneof} + } + return m0 +} + +type case_M2Outer_OuterOneof protoreflect.FieldNumber + +func (x case_M2Outer_OuterOneof) String() string { + md := file_proto2test_proto_msgTypes[3].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isM2Outer_OuterOneof interface { + isM2Outer_OuterOneof() +} + +type M2Outer_InnerMsg struct { + InnerMsg *M2Outer_MInner `protobuf:"bytes,1,opt,name=inner_msg,json=innerMsg,oneof"` +} + +type M2Outer_StringOneof struct { + StringOneof string `protobuf:"bytes,2,opt,name=string_oneof,json=stringOneof,oneof"` +} + +func (*M2Outer_InnerMsg) isM2Outer_OuterOneof() {} + +func (*M2Outer_StringOneof) isM2Outer_OuterOneof() {} + +type ConflictingOneof struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Types that are valid to be assigned to Included: + // + // *ConflictingOneof_Sub_ + // *ConflictingOneof_Otherwise + Included isConflictingOneof_Included `protobuf_oneof:"included"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConflictingOneof) Reset() { + *x = ConflictingOneof{} + mi := &file_proto2test_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConflictingOneof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConflictingOneof) ProtoMessage() {} + +func (x *ConflictingOneof) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ConflictingOneof) GetIncluded() isConflictingOneof_Included { + if x != nil { + return x.Included + } + return nil +} + +func (x *ConflictingOneof) GetSub() *ConflictingOneof_Sub { + if x != nil { + if x, ok := x.Included.(*ConflictingOneof_Sub_); ok { + return x.Sub + } + } + return nil +} + +func (x *ConflictingOneof) GetOtherwise() string { + if x != nil { + if x, ok := x.Included.(*ConflictingOneof_Otherwise); ok { + return x.Otherwise + } + } + return "" +} + +func (x *ConflictingOneof) SetSub(v *ConflictingOneof_Sub) { + if v == nil { + x.Included = nil + return + } + x.Included = &ConflictingOneof_Sub_{v} +} + +func (x *ConflictingOneof) SetOtherwise(v string) { + x.Included = &ConflictingOneof_Otherwise{v} +} + +func (x *ConflictingOneof) HasIncluded() bool { + if x == nil { + return false + } + return x.Included != nil +} + +func (x *ConflictingOneof) HasSub() bool { + if x == nil { + return false + } + _, ok := x.Included.(*ConflictingOneof_Sub_) + return ok +} + +func (x *ConflictingOneof) HasOtherwise() bool { + if x == nil { + return false + } + _, ok := x.Included.(*ConflictingOneof_Otherwise) + return ok +} + +func (x *ConflictingOneof) ClearIncluded() { + x.Included = nil +} + +func (x *ConflictingOneof) ClearSub() { + if _, ok := x.Included.(*ConflictingOneof_Sub_); ok { + x.Included = nil + } +} + +func (x *ConflictingOneof) ClearOtherwise() { + if _, ok := x.Included.(*ConflictingOneof_Otherwise); ok { + x.Included = nil + } +} + +const ConflictingOneof_Included_not_set_case case_ConflictingOneof_Included = 0 +const ConflictingOneof_Sub_case case_ConflictingOneof_Included = 1 +const ConflictingOneof_Otherwise_case case_ConflictingOneof_Included = 2 + +func (x *ConflictingOneof) WhichIncluded() case_ConflictingOneof_Included { + if x == nil { + return ConflictingOneof_Included_not_set_case + } + switch x.Included.(type) { + case *ConflictingOneof_Sub_: + return ConflictingOneof_Sub_case + case *ConflictingOneof_Otherwise: + return ConflictingOneof_Otherwise_case + default: + return ConflictingOneof_Included_not_set_case + } +} + +type ConflictingOneof_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof Included: + Sub *ConflictingOneof_Sub + Otherwise *string + // -- end of Included +} + +func (b0 ConflictingOneof_builder) Build() *ConflictingOneof { + m0 := &ConflictingOneof{} + b, x := &b0, m0 + _, _ = b, x + if b.Sub != nil { + x.Included = &ConflictingOneof_Sub_{b.Sub} + } + if b.Otherwise != nil { + x.Included = &ConflictingOneof_Otherwise{*b.Otherwise} + } + return m0 +} + +type case_ConflictingOneof_Included protoreflect.FieldNumber + +func (x case_ConflictingOneof_Included) String() string { + md := file_proto2test_proto_msgTypes[4].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isConflictingOneof_Included interface { + isConflictingOneof_Included() +} + +type ConflictingOneof_Sub_ struct { + Sub *ConflictingOneof_Sub `protobuf:"bytes,1,opt,name=sub,oneof"` +} + +type ConflictingOneof_Otherwise struct { + Otherwise string `protobuf:"bytes,2,opt,name=otherwise,oneof"` +} + +func (*ConflictingOneof_Sub_) isConflictingOneof_Included() {} + +func (*ConflictingOneof_Otherwise) isConflictingOneof_Included() {} + +type SetterNameConflict struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + Stat *int32 `protobuf:"varint,1,opt,name=stat" json:"stat,omitempty"` + SetStat *int32 `protobuf:"varint,2,opt,name=set_stat,json=setStat" json:"set_stat,omitempty"` + GetStat_ *int32 `protobuf:"varint,3,opt,name=get_stat,json=getStat" json:"get_stat,omitempty"` + HasStat *int32 `protobuf:"varint,4,opt,name=has_stat,json=hasStat" json:"has_stat,omitempty"` + ClearStat *int32 `protobuf:"varint,5,opt,name=clear_stat,json=clearStat" json:"clear_stat,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetterNameConflict) Reset() { + *x = SetterNameConflict{} + mi := &file_proto2test_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetterNameConflict) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetterNameConflict) ProtoMessage() {} + +func (x *SetterNameConflict) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *SetterNameConflict) Get_Stat() int32 { + if x != nil && x.Stat != nil { + return *x.Stat + } + return 0 +} + +// Deprecated: Use Get_Stat instead. +func (x *SetterNameConflict) GetStat() int32 { + return x.Get_Stat() +} + +func (x *SetterNameConflict) GetSetStat() int32 { + if x != nil && x.SetStat != nil { + return *x.SetStat + } + return 0 +} + +func (x *SetterNameConflict) GetGetStat() int32 { + if x != nil && x.GetStat_ != nil { + return *x.GetStat_ + } + return 0 +} + +// Deprecated: Use GetGetStat instead. +func (x *SetterNameConflict) GetGetStat_() int32 { + return x.GetGetStat() +} + +func (x *SetterNameConflict) GetHasStat() int32 { + if x != nil && x.HasStat != nil { + return *x.HasStat + } + return 0 +} + +func (x *SetterNameConflict) GetClearStat() int32 { + if x != nil && x.ClearStat != nil { + return *x.ClearStat + } + return 0 +} + +func (x *SetterNameConflict) Set_Stat(v int32) { + x.Stat = &v +} + +func (x *SetterNameConflict) SetSetStat(v int32) { + x.SetStat = &v +} + +func (x *SetterNameConflict) SetGetStat(v int32) { + x.GetStat_ = &v +} + +func (x *SetterNameConflict) SetHasStat(v int32) { + x.HasStat = &v +} + +func (x *SetterNameConflict) SetClearStat(v int32) { + x.ClearStat = &v +} + +func (x *SetterNameConflict) Has_Stat() bool { + if x == nil { + return false + } + return x.Stat != nil +} + +func (x *SetterNameConflict) HasSetStat() bool { + if x == nil { + return false + } + return x.SetStat != nil +} + +func (x *SetterNameConflict) HasGetStat() bool { + if x == nil { + return false + } + return x.GetStat_ != nil +} + +func (x *SetterNameConflict) HasHasStat() bool { + if x == nil { + return false + } + return x.HasStat != nil +} + +func (x *SetterNameConflict) HasClearStat() bool { + if x == nil { + return false + } + return x.ClearStat != nil +} + +func (x *SetterNameConflict) Clear_Stat() { + x.Stat = nil +} + +func (x *SetterNameConflict) ClearSetStat() { + x.SetStat = nil +} + +func (x *SetterNameConflict) ClearGetStat() { + x.GetStat_ = nil +} + +func (x *SetterNameConflict) ClearHasStat() { + x.HasStat = nil +} + +func (x *SetterNameConflict) ClearClearStat() { + x.ClearStat = nil +} + +type SetterNameConflict_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Stat *int32 + SetStat *int32 + GetStat *int32 + HasStat *int32 + ClearStat *int32 +} + +func (b0 SetterNameConflict_builder) Build() *SetterNameConflict { + m0 := &SetterNameConflict{} + b, x := &b0, m0 + _, _ = b, x + x.Stat = b.Stat + x.SetStat = b.SetStat + x.GetStat_ = b.GetStat + x.HasStat = b.HasStat + x.ClearStat = b.ClearStat + return m0 +} + +type M2Outer_MInner struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Types that are valid to be assigned to InnerOneof: + // + // *M2Outer_MInner_StringInner + // *M2Outer_MInner_IntInner + InnerOneof isM2Outer_MInner_InnerOneof `protobuf_oneof:"inner_oneof"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *M2Outer_MInner) Reset() { + *x = M2Outer_MInner{} + mi := &file_proto2test_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *M2Outer_MInner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*M2Outer_MInner) ProtoMessage() {} + +func (x *M2Outer_MInner) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *M2Outer_MInner) GetInnerOneof() isM2Outer_MInner_InnerOneof { + if x != nil { + return x.InnerOneof + } + return nil +} + +func (x *M2Outer_MInner) GetStringInner() string { + if x != nil { + if x, ok := x.InnerOneof.(*M2Outer_MInner_StringInner); ok { + return x.StringInner + } + } + return "" +} + +func (x *M2Outer_MInner) GetIntInner() int64 { + if x != nil { + if x, ok := x.InnerOneof.(*M2Outer_MInner_IntInner); ok { + return x.IntInner + } + } + return 0 +} + +func (x *M2Outer_MInner) SetStringInner(v string) { + x.InnerOneof = &M2Outer_MInner_StringInner{v} +} + +func (x *M2Outer_MInner) SetIntInner(v int64) { + x.InnerOneof = &M2Outer_MInner_IntInner{v} +} + +func (x *M2Outer_MInner) HasInnerOneof() bool { + if x == nil { + return false + } + return x.InnerOneof != nil +} + +func (x *M2Outer_MInner) HasStringInner() bool { + if x == nil { + return false + } + _, ok := x.InnerOneof.(*M2Outer_MInner_StringInner) + return ok +} + +func (x *M2Outer_MInner) HasIntInner() bool { + if x == nil { + return false + } + _, ok := x.InnerOneof.(*M2Outer_MInner_IntInner) + return ok +} + +func (x *M2Outer_MInner) ClearInnerOneof() { + x.InnerOneof = nil +} + +func (x *M2Outer_MInner) ClearStringInner() { + if _, ok := x.InnerOneof.(*M2Outer_MInner_StringInner); ok { + x.InnerOneof = nil + } +} + +func (x *M2Outer_MInner) ClearIntInner() { + if _, ok := x.InnerOneof.(*M2Outer_MInner_IntInner); ok { + x.InnerOneof = nil + } +} + +const M2Outer_MInner_InnerOneof_not_set_case case_M2Outer_MInner_InnerOneof = 0 +const M2Outer_MInner_StringInner_case case_M2Outer_MInner_InnerOneof = 19 +const M2Outer_MInner_IntInner_case case_M2Outer_MInner_InnerOneof = 20 + +func (x *M2Outer_MInner) WhichInnerOneof() case_M2Outer_MInner_InnerOneof { + if x == nil { + return M2Outer_MInner_InnerOneof_not_set_case + } + switch x.InnerOneof.(type) { + case *M2Outer_MInner_StringInner: + return M2Outer_MInner_StringInner_case + case *M2Outer_MInner_IntInner: + return M2Outer_MInner_IntInner_case + default: + return M2Outer_MInner_InnerOneof_not_set_case + } +} + +type M2Outer_MInner_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof InnerOneof: + StringInner *string + IntInner *int64 + // -- end of InnerOneof +} + +func (b0 M2Outer_MInner_builder) Build() *M2Outer_MInner { + m0 := &M2Outer_MInner{} + b, x := &b0, m0 + _, _ = b, x + if b.StringInner != nil { + x.InnerOneof = &M2Outer_MInner_StringInner{*b.StringInner} + } + if b.IntInner != nil { + x.InnerOneof = &M2Outer_MInner_IntInner{*b.IntInner} + } + return m0 +} + +type case_M2Outer_MInner_InnerOneof protoreflect.FieldNumber + +func (x case_M2Outer_MInner_InnerOneof) String() string { + md := file_proto2test_proto_msgTypes[8].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isM2Outer_MInner_InnerOneof interface { + isM2Outer_MInner_InnerOneof() +} + +type M2Outer_MInner_StringInner struct { + StringInner string `protobuf:"bytes,19,opt,name=string_inner,json=stringInner,oneof"` +} + +type M2Outer_MInner_IntInner struct { + IntInner int64 `protobuf:"varint,20,opt,name=int_inner,json=intInner,oneof"` +} + +func (*M2Outer_MInner_StringInner) isM2Outer_MInner_InnerOneof() {} + +func (*M2Outer_MInner_IntInner) isM2Outer_MInner_InnerOneof() {} + +type ConflictingOneof_Sub struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConflictingOneof_Sub) Reset() { + *x = ConflictingOneof_Sub{} + mi := &file_proto2test_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConflictingOneof_Sub) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConflictingOneof_Sub) ProtoMessage() {} + +func (x *ConflictingOneof_Sub) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +type ConflictingOneof_Sub_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + +} + +func (b0 ConflictingOneof_Sub_builder) Build() *ConflictingOneof_Sub { + m0 := &ConflictingOneof_Sub{} + b, x := &b0, m0 + _, _ = b, x + return m0 +} + +type ConflictingOneof_DeepSub struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + // Types that are valid to be assigned to DeeplyIncluded: + // + // *ConflictingOneof_DeepSub_Sub_ + // *ConflictingOneof_DeepSub_Otherwise + DeeplyIncluded isConflictingOneof_DeepSub_DeeplyIncluded `protobuf_oneof:"deeply_included"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConflictingOneof_DeepSub) Reset() { + *x = ConflictingOneof_DeepSub{} + mi := &file_proto2test_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConflictingOneof_DeepSub) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConflictingOneof_DeepSub) ProtoMessage() {} + +func (x *ConflictingOneof_DeepSub) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *ConflictingOneof_DeepSub) GetDeeplyIncluded() isConflictingOneof_DeepSub_DeeplyIncluded { + if x != nil { + return x.DeeplyIncluded + } + return nil +} + +func (x *ConflictingOneof_DeepSub) GetSub() *ConflictingOneof_DeepSub_Sub { + if x != nil { + if x, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_); ok { + return x.Sub + } + } + return nil +} + +func (x *ConflictingOneof_DeepSub) GetOtherwise() string { + if x != nil { + if x, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise); ok { + return x.Otherwise + } + } + return "" +} + +func (x *ConflictingOneof_DeepSub) SetSub(v *ConflictingOneof_DeepSub_Sub) { + if v == nil { + x.DeeplyIncluded = nil + return + } + x.DeeplyIncluded = &ConflictingOneof_DeepSub_Sub_{v} +} + +func (x *ConflictingOneof_DeepSub) SetOtherwise(v string) { + x.DeeplyIncluded = &ConflictingOneof_DeepSub_Otherwise{v} +} + +func (x *ConflictingOneof_DeepSub) HasDeeplyIncluded() bool { + if x == nil { + return false + } + return x.DeeplyIncluded != nil +} + +func (x *ConflictingOneof_DeepSub) HasSub() bool { + if x == nil { + return false + } + _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_) + return ok +} + +func (x *ConflictingOneof_DeepSub) HasOtherwise() bool { + if x == nil { + return false + } + _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise) + return ok +} + +func (x *ConflictingOneof_DeepSub) ClearDeeplyIncluded() { + x.DeeplyIncluded = nil +} + +func (x *ConflictingOneof_DeepSub) ClearSub() { + if _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Sub_); ok { + x.DeeplyIncluded = nil + } +} + +func (x *ConflictingOneof_DeepSub) ClearOtherwise() { + if _, ok := x.DeeplyIncluded.(*ConflictingOneof_DeepSub_Otherwise); ok { + x.DeeplyIncluded = nil + } +} + +const ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 0 +const ConflictingOneof_DeepSub_Sub_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 1 +const ConflictingOneof_DeepSub_Otherwise_case case_ConflictingOneof_DeepSub_DeeplyIncluded = 3 + +func (x *ConflictingOneof_DeepSub) WhichDeeplyIncluded() case_ConflictingOneof_DeepSub_DeeplyIncluded { + if x == nil { + return ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case + } + switch x.DeeplyIncluded.(type) { + case *ConflictingOneof_DeepSub_Sub_: + return ConflictingOneof_DeepSub_Sub_case + case *ConflictingOneof_DeepSub_Otherwise: + return ConflictingOneof_DeepSub_Otherwise_case + default: + return ConflictingOneof_DeepSub_DeeplyIncluded_not_set_case + } +} + +type ConflictingOneof_DeepSub_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + // Fields of oneof DeeplyIncluded: + Sub *ConflictingOneof_DeepSub_Sub + Otherwise *string + // -- end of DeeplyIncluded +} + +func (b0 ConflictingOneof_DeepSub_builder) Build() *ConflictingOneof_DeepSub { + m0 := &ConflictingOneof_DeepSub{} + b, x := &b0, m0 + _, _ = b, x + if b.Sub != nil { + x.DeeplyIncluded = &ConflictingOneof_DeepSub_Sub_{b.Sub} + } + if b.Otherwise != nil { + x.DeeplyIncluded = &ConflictingOneof_DeepSub_Otherwise{*b.Otherwise} + } + return m0 +} + +type case_ConflictingOneof_DeepSub_DeeplyIncluded protoreflect.FieldNumber + +func (x case_ConflictingOneof_DeepSub_DeeplyIncluded) String() string { + md := file_proto2test_proto_msgTypes[10].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isConflictingOneof_DeepSub_DeeplyIncluded interface { + isConflictingOneof_DeepSub_DeeplyIncluded() +} + +type ConflictingOneof_DeepSub_Sub_ struct { + Sub *ConflictingOneof_DeepSub_Sub `protobuf:"bytes,1,opt,name=sub,oneof"` +} + +type ConflictingOneof_DeepSub_Otherwise struct { + Otherwise string `protobuf:"bytes,3,opt,name=otherwise,oneof"` +} + +func (*ConflictingOneof_DeepSub_Sub_) isConflictingOneof_DeepSub_DeeplyIncluded() {} + +func (*ConflictingOneof_DeepSub_Otherwise) isConflictingOneof_DeepSub_DeeplyIncluded() {} + +type ConflictingOneof_DeepSub_Sub struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConflictingOneof_DeepSub_Sub) Reset() { + *x = ConflictingOneof_DeepSub_Sub{} + mi := &file_proto2test_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConflictingOneof_DeepSub_Sub) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConflictingOneof_DeepSub_Sub) ProtoMessage() {} + +func (x *ConflictingOneof_DeepSub_Sub) ProtoReflect() protoreflect.Message { + mi := &file_proto2test_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +type ConflictingOneof_DeepSub_Sub_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + +} + +func (b0 ConflictingOneof_DeepSub_Sub_builder) Build() *ConflictingOneof_DeepSub_Sub { + m0 := &ConflictingOneof_DeepSub_Sub{} + b, x := &b0, m0 + _, _ = b, x + return m0 +} + +var File_proto2test_proto protoreflect.FileDescriptor + +var file_proto2test_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x22, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, + 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x27, 0x0a, 0x0e, 0x44, 0x6f, 0x4e, + 0x6f, 0x74, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x62, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, + 0x10, 0x01, 0x22, 0xa0, 0x09, 0x0a, 0x02, 0x4d, 0x32, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, + 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x66, 0x33, 0x32, 0x12, + 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x36, + 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, + 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x36, + 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, 0x36, 0x34, 0x12, 0x0c, 0x0a, + 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, 0x12, 0x34, 0x0a, 0x01, 0x6d, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x52, 0x01, + 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x02, 0x69, + 0x73, 0x12, 0x36, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x41, 0x0a, 0x03, 0x6d, 0x61, 0x70, + 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e, 0x4d, + 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x12, 0x39, 0x0a, 0x01, + 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e, + 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09, + 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48, + 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x45, 0x0a, 0x09, 0x6d, + 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, + 0x6f, 0x66, 0x12, 0x4c, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, + 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, + 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, + 0x65, 0x6f, 0x66, 0x12, 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x32, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x1f, 0x0a, 0x0a, 0x69, 0x6e, + 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, + 0x52, 0x09, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x47, 0x0a, 0x0a, 0x6d, + 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x32, 0x48, 0x01, 0x52, 0x09, 0x6d, 0x73, 0x67, 0x4f, 0x6e, + 0x65, 0x6f, 0x66, 0x32, 0x12, 0x4e, 0x0a, 0x0b, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x32, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6e, 0x65, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, + 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4d, + 0x32, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x01, 0x52, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, + 0x65, 0x6f, 0x66, 0x32, 0x12, 0x23, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x32, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x0b, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e, + 0x75, 0x6d, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x3a, 0x07, 0x62, + 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d, 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0e, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0x85, 0x0a, 0x0a, 0x0b, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, + 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a, + 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, + 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36, + 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, 0x12, 0x3d, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, + 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, 0x03, + 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x3f, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, + 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x4a, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, + 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, + 0x6d, 0x61, 0x70, 0x12, 0x47, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, 0x12, 0x23, 0x0a, 0x0c, + 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0e, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, + 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, + 0x12, 0x4e, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, + 0x12, 0x5a, 0x0a, 0x0a, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, + 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x48, + 0x00, 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, + 0x25, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x1f, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x32, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x09, 0x69, 0x6e, + 0x74, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x50, 0x0a, 0x0a, 0x6d, 0x73, 0x67, 0x5f, 0x6f, + 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x48, 0x01, 0x52, 0x09, + 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x5c, 0x0a, 0x0b, 0x65, 0x6e, 0x75, + 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x39, + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, + 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x01, 0x52, 0x0a, 0x65, 0x6e, 0x75, + 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x23, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, + 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x32, 0x12, 0x14, 0x0a, 0x05, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x75, 0x69, + 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, + 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x16, 0x0a, + 0x09, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, + 0x56, 0x41, 0x4c, 0x10, 0x00, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d, + 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x0e, 0x0a, + 0x0c, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0xff, 0x01, + 0x0a, 0x07, 0x4d, 0x32, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x51, 0x0a, 0x09, 0x69, 0x6e, 0x6e, + 0x65, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6e, + 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, + 0x74, 0x2e, 0x4d, 0x32, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x4d, 0x49, 0x6e, 0x6e, 0x65, 0x72, + 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 0x0c, + 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, + 0x66, 0x1a, 0x64, 0x0a, 0x06, 0x4d, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0c, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x6e, 0x65, 0x72, + 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x3a, + 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x6e, 0x65, + 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, + 0x42, 0x0d, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x22, + 0xd3, 0x02, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4f, + 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4c, 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x38, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, + 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, + 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x53, 0x75, 0x62, 0x48, 0x00, 0x52, 0x03, 0x73, + 0x75, 0x62, 0x12, 0x1e, 0x0a, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, + 0x73, 0x65, 0x1a, 0x0e, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, + 0x10, 0x02, 0x1a, 0xab, 0x01, 0x0a, 0x07, 0x44, 0x65, 0x65, 0x70, 0x53, 0x75, 0x62, 0x12, 0x54, + 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x65, 0x6f, + 0x66, 0x2e, 0x44, 0x65, 0x65, 0x70, 0x53, 0x75, 0x62, 0x2e, 0x53, 0x75, 0x62, 0x48, 0x00, 0x52, + 0x03, 0x73, 0x75, 0x62, 0x12, 0x1e, 0x0a, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x77, 0x69, 0x73, 0x65, 0x1a, 0x0e, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x3a, 0x07, 0x62, 0x05, 0xd2, + 0x3e, 0x02, 0x10, 0x02, 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x11, 0x0a, + 0x0f, 0x64, 0x65, 0x65, 0x70, 0x6c, 0x79, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, + 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x42, 0x0a, 0x0a, 0x08, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x74, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x07, 0x73, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x67, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x68, 0x61, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x3a, 0x07, 0x62, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x02, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, +} + +var ( + file_proto2test_proto_rawDescOnce sync.Once + file_proto2test_proto_rawDescData = file_proto2test_proto_rawDesc +) + +func file_proto2test_proto_rawDescGZIP() []byte { + file_proto2test_proto_rawDescOnce.Do(func() { + file_proto2test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto2test_proto_rawDescData) + }) + return file_proto2test_proto_rawDescData +} + +var file_proto2test_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_proto2test_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_proto2test_proto_goTypes = []any{ + (M2_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test.M2.Enum + (OtherProto2_OtherEnum)(0), // 1: net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum + (*DoNotMigrateMe)(nil), // 2: net.proto2.go.open2opaque.o2o.test.DoNotMigrateMe + (*M2)(nil), // 3: net.proto2.go.open2opaque.o2o.test.M2 + (*OtherProto2)(nil), // 4: net.proto2.go.open2opaque.o2o.test.OtherProto2 + (*M2Outer)(nil), // 5: net.proto2.go.open2opaque.o2o.test.M2Outer + (*ConflictingOneof)(nil), // 6: net.proto2.go.open2opaque.o2o.test.ConflictingOneof + (*SetterNameConflict)(nil), // 7: net.proto2.go.open2opaque.o2o.test.SetterNameConflict + nil, // 8: net.proto2.go.open2opaque.o2o.test.M2.MapEntry + nil, // 9: net.proto2.go.open2opaque.o2o.test.OtherProto2.MapEntry + (*M2Outer_MInner)(nil), // 10: net.proto2.go.open2opaque.o2o.test.M2Outer.MInner + (*ConflictingOneof_Sub)(nil), // 11: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.Sub + (*ConflictingOneof_DeepSub)(nil), // 12: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub + (*ConflictingOneof_DeepSub_Sub)(nil), // 13: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.Sub +} +var file_proto2test_proto_depIdxs = []int32{ + 3, // 0: net.proto2.go.open2opaque.o2o.test.M2.m:type_name -> net.proto2.go.open2opaque.o2o.test.M2 + 3, // 1: net.proto2.go.open2opaque.o2o.test.M2.ms:type_name -> net.proto2.go.open2opaque.o2o.test.M2 + 8, // 2: net.proto2.go.open2opaque.o2o.test.M2.map:type_name -> net.proto2.go.open2opaque.o2o.test.M2.MapEntry + 0, // 3: net.proto2.go.open2opaque.o2o.test.M2.e:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum + 3, // 4: net.proto2.go.open2opaque.o2o.test.M2.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.M2 + 0, // 5: net.proto2.go.open2opaque.o2o.test.M2.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum + 3, // 6: net.proto2.go.open2opaque.o2o.test.M2.msg_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.M2 + 0, // 7: net.proto2.go.open2opaque.o2o.test.M2.enum_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.M2.Enum + 4, // 8: net.proto2.go.open2opaque.o2o.test.OtherProto2.m:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2 + 4, // 9: net.proto2.go.open2opaque.o2o.test.OtherProto2.ms:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2 + 9, // 10: net.proto2.go.open2opaque.o2o.test.OtherProto2.map:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.MapEntry + 1, // 11: net.proto2.go.open2opaque.o2o.test.OtherProto2.e:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum + 4, // 12: net.proto2.go.open2opaque.o2o.test.OtherProto2.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2 + 1, // 13: net.proto2.go.open2opaque.o2o.test.OtherProto2.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum + 4, // 14: net.proto2.go.open2opaque.o2o.test.OtherProto2.msg_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2 + 1, // 15: net.proto2.go.open2opaque.o2o.test.OtherProto2.enum_oneof2:type_name -> net.proto2.go.open2opaque.o2o.test.OtherProto2.OtherEnum + 10, // 16: net.proto2.go.open2opaque.o2o.test.M2Outer.inner_msg:type_name -> net.proto2.go.open2opaque.o2o.test.M2Outer.MInner + 11, // 17: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.sub:type_name -> net.proto2.go.open2opaque.o2o.test.ConflictingOneof.Sub + 13, // 18: net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.sub:type_name -> net.proto2.go.open2opaque.o2o.test.ConflictingOneof.DeepSub.Sub + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_proto2test_proto_init() } +func file_proto2test_proto_init() { + if File_proto2test_proto != nil { + return + } + file_proto2test_proto_msgTypes[1].OneofWrappers = []any{ + (*M2_StringOneof)(nil), + (*M2_IntOneof)(nil), + (*M2_MsgOneof)(nil), + (*M2_EnumOneof)(nil), + (*M2_BytesOneof)(nil), + (*M2_StringOneof2)(nil), + (*M2_IntOneof2)(nil), + (*M2_MsgOneof2)(nil), + (*M2_EnumOneof2)(nil), + (*M2_BytesOneof2)(nil), + } + file_proto2test_proto_msgTypes[2].OneofWrappers = []any{ + (*OtherProto2_StringOneof)(nil), + (*OtherProto2_IntOneof)(nil), + (*OtherProto2_MsgOneof)(nil), + (*OtherProto2_EnumOneof)(nil), + (*OtherProto2_BytesOneof)(nil), + (*OtherProto2_StringOneof2)(nil), + (*OtherProto2_IntOneof2)(nil), + (*OtherProto2_MsgOneof2)(nil), + (*OtherProto2_EnumOneof2)(nil), + (*OtherProto2_BytesOneof2)(nil), + } + file_proto2test_proto_msgTypes[3].OneofWrappers = []any{ + (*M2Outer_InnerMsg)(nil), + (*M2Outer_StringOneof)(nil), + } + file_proto2test_proto_msgTypes[4].OneofWrappers = []any{ + (*ConflictingOneof_Sub_)(nil), + (*ConflictingOneof_Otherwise)(nil), + } + file_proto2test_proto_msgTypes[8].OneofWrappers = []any{ + (*M2Outer_MInner_StringInner)(nil), + (*M2Outer_MInner_IntInner)(nil), + } + file_proto2test_proto_msgTypes[10].OneofWrappers = []any{ + (*ConflictingOneof_DeepSub_Sub_)(nil), + (*ConflictingOneof_DeepSub_Otherwise)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto2test_proto_rawDesc, + NumEnums: 2, + NumMessages: 12, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto2test_proto_goTypes, + DependencyIndexes: file_proto2test_proto_depIdxs, + EnumInfos: file_proto2test_proto_enumTypes, + MessageInfos: file_proto2test_proto_msgTypes, + }.Build() + File_proto2test_proto = out.File + file_proto2test_proto_rawDesc = nil + file_proto2test_proto_goTypes = nil + file_proto2test_proto_depIdxs = nil +}
diff --git a/internal/fix/testdata/proto2test_go_proto/proto2test.proto b/internal/fix/testdata/proto2test_go_proto/proto2test.proto new file mode 100644 index 0000000..e4c93d3 --- /dev/null +++ b/internal/fix/testdata/proto2test_go_proto/proto2test.proto
@@ -0,0 +1,150 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.o2o.test; + +import "google/protobuf/go_features.proto"; + +message DoNotMigrateMe { + option features.(pb.go).api_level = API_OPEN; + + bool b = 1; +} + +message M2 { + option features.(pb.go).api_level = API_HYBRID; + + bool b = 1; + bytes bytes = 2; + float f32 = 3; + double f64 = 4; + int32 i32 = 5; + int64 i64 = 6; + uint32 ui32 = 7; + uint64 ui64 = 8; + string s = 9; + M2 m = 10; + repeated int32 is = 11; + repeated M2 ms = 12; + map<string, bool> map = 29; + enum Enum { + E_VAL = 0; + } + Enum e = 13; + oneof oneof_field { + string string_oneof = 14; + int64 int_oneof = 15; + M2 msg_oneof = 16; + Enum enum_oneof = 17; + bytes bytes_oneof = 18; + } + oneof oneof_field2 { + string string_oneof2 = 19; + int64 int_oneof2 = 20; + M2 msg_oneof2 = 21; + Enum enum_oneof2 = 22; + bytes bytes_oneof2 = 23; + } + int32 build = 24; + int32 proto_message = 25; + int32 reset = 26; + int32 string = 27; + int32 descriptor = 28; +} + +message OtherProto2 { + option features.(pb.go).api_level = API_HYBRID; + + bool b = 1; + bytes bytes = 2; + float f32 = 3; + double f64 = 4; + int32 i32 = 5; + int64 i64 = 6; + uint32 ui32 = 7; + uint64 ui64 = 8; + string s = 9; + OtherProto2 m = 10; + repeated int32 is = 11; + repeated OtherProto2 ms = 12; + map<string, bool> map = 29; + enum OtherEnum { + E_VAL = 0; + } + OtherEnum e = 13; + oneof oneof_field { + string string_oneof = 14; + int64 int_oneof = 15; + OtherProto2 msg_oneof = 16; + OtherEnum enum_oneof = 17; + bytes bytes_oneof = 18; + } + oneof oneof_field2 { + string string_oneof2 = 19; + int64 int_oneof2 = 20; + OtherProto2 msg_oneof2 = 21; + OtherEnum enum_oneof2 = 22; + bytes bytes_oneof2 = 23; + } + int32 build = 24; + int32 proto_message = 25; + int32 reset = 26; + int32 string = 27; + int32 descriptor = 28; +} + +message M2Outer { + option features.(pb.go).api_level = API_HYBRID; + + message MInner { + option features.(pb.go).api_level = API_HYBRID; + + oneof inner_oneof { + string string_inner = 19; + int64 int_inner = 20; + } + } + oneof outer_oneof { + MInner inner_msg = 1; + string string_oneof = 2; + } +} + +message ConflictingOneof { + option features.(pb.go).api_level = API_HYBRID; + + message Sub { + option features.(pb.go).api_level = API_HYBRID; + } + + oneof included { + Sub sub = 1; + string otherwise = 2; + } + + message DeepSub { + option features.(pb.go).api_level = API_HYBRID; + + message Sub { + option features.(pb.go).api_level = API_HYBRID; + } + + oneof deeply_included { + Sub sub = 1; + string otherwise = 3; + } + } +} + +message SetterNameConflict { + option features.(pb.go).api_level = API_HYBRID; + + int32 stat = 1; + int32 set_stat = 2; + int32 get_stat = 3; + int32 has_stat = 4; + int32 clear_stat = 5; +}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go b/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go new file mode 100644 index 0000000..95d9148 --- /dev/null +++ b/internal/fix/testdata/proto3test_go_proto/proto3test.pb.go
@@ -0,0 +1,950 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: proto3test.proto + +//go:build !protoopaque + +package proto3test_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type M3_Enum int32 + +const ( + M3_E_VAL M3_Enum = 0 +) + +// Enum value maps for M3_Enum. +var ( + M3_Enum_name = map[int32]string{ + 0: "E_VAL", + } + M3_Enum_value = map[string]int32{ + "E_VAL": 0, + } +) + +func (x M3_Enum) Enum() *M3_Enum { + p := new(M3_Enum) + *p = x + return p +} + +func (x M3_Enum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (M3_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_proto3test_proto_enumTypes[0].Descriptor() +} + +func (M3_Enum) Type() protoreflect.EnumType { + return &file_proto3test_proto_enumTypes[0] +} + +func (x M3_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type M3 struct { + state protoimpl.MessageState `protogen:"hybrid.v1"` + B bool `protobuf:"varint,1,opt,name=b,proto3" json:"b,omitempty"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"` + F32 float32 `protobuf:"fixed32,3,opt,name=f32,proto3" json:"f32,omitempty"` + F64 float64 `protobuf:"fixed64,4,opt,name=f64,proto3" json:"f64,omitempty"` + I32 int32 `protobuf:"varint,5,opt,name=i32,proto3" json:"i32,omitempty"` + I64 int64 `protobuf:"varint,6,opt,name=i64,proto3" json:"i64,omitempty"` + Ui32 uint32 `protobuf:"varint,7,opt,name=ui32,proto3" json:"ui32,omitempty"` + Ui64 uint64 `protobuf:"varint,8,opt,name=ui64,proto3" json:"ui64,omitempty"` + S string `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` + M *M3 `protobuf:"bytes,10,opt,name=m,proto3" json:"m,omitempty"` + Is []int32 `protobuf:"varint,11,rep,packed,name=is,proto3" json:"is,omitempty"` + Ms []*M3 `protobuf:"bytes,12,rep,name=ms,proto3" json:"ms,omitempty"` + Map map[string]bool `protobuf:"bytes,29,rep,name=map,proto3" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + E M3_Enum `protobuf:"varint,13,opt,name=e,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum" json:"e,omitempty"` + // Types that are valid to be assigned to OneofField: + // + // *M3_StringOneof + // *M3_IntOneof + // *M3_MsgOneof + // *M3_EnumOneof + // *M3_BytesOneof + // *M3_Build + // *M3_ProtoMessage_ + // *M3_Reset_ + // *M3_String_ + // *M3_Descriptor_ + OneofField isM3_OneofField `protobuf_oneof:"oneof_field"` + SecondI32 int32 `protobuf:"varint,30,opt,name=second_i32,json=secondI32,proto3" json:"second_i32,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *M3) Reset() { + *x = M3{} + mi := &file_proto3test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *M3) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*M3) ProtoMessage() {} + +func (x *M3) ProtoReflect() protoreflect.Message { + mi := &file_proto3test_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *M3) GetB() bool { + if x != nil { + return x.B + } + return false +} + +func (x *M3) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +func (x *M3) GetF32() float32 { + if x != nil { + return x.F32 + } + return 0 +} + +func (x *M3) GetF64() float64 { + if x != nil { + return x.F64 + } + return 0 +} + +func (x *M3) GetI32() int32 { + if x != nil { + return x.I32 + } + return 0 +} + +func (x *M3) GetI64() int64 { + if x != nil { + return x.I64 + } + return 0 +} + +func (x *M3) GetUi32() uint32 { + if x != nil { + return x.Ui32 + } + return 0 +} + +func (x *M3) GetUi64() uint64 { + if x != nil { + return x.Ui64 + } + return 0 +} + +func (x *M3) GetS() string { + if x != nil { + return x.S + } + return "" +} + +func (x *M3) GetM() *M3 { + if x != nil { + return x.M + } + return nil +} + +func (x *M3) GetIs() []int32 { + if x != nil { + return x.Is + } + return nil +} + +func (x *M3) GetMs() []*M3 { + if x != nil { + return x.Ms + } + return nil +} + +func (x *M3) GetMap() map[string]bool { + if x != nil { + return x.Map + } + return nil +} + +func (x *M3) GetE() M3_Enum { + if x != nil { + return x.E + } + return M3_E_VAL +} + +func (x *M3) GetOneofField() isM3_OneofField { + if x != nil { + return x.OneofField + } + return nil +} + +func (x *M3) GetStringOneof() string { + if x != nil { + if x, ok := x.OneofField.(*M3_StringOneof); ok { + return x.StringOneof + } + } + return "" +} + +func (x *M3) GetIntOneof() int64 { + if x != nil { + if x, ok := x.OneofField.(*M3_IntOneof); ok { + return x.IntOneof + } + } + return 0 +} + +func (x *M3) GetMsgOneof() *M3 { + if x != nil { + if x, ok := x.OneofField.(*M3_MsgOneof); ok { + return x.MsgOneof + } + } + return nil +} + +func (x *M3) GetEnumOneof() M3_Enum { + if x != nil { + if x, ok := x.OneofField.(*M3_EnumOneof); ok { + return x.EnumOneof + } + } + return M3_E_VAL +} + +func (x *M3) GetBytesOneof() []byte { + if x != nil { + if x, ok := x.OneofField.(*M3_BytesOneof); ok { + return x.BytesOneof + } + } + return nil +} + +func (x *M3) GetBuild_() int32 { + if x != nil { + if x, ok := x.OneofField.(*M3_Build); ok { + return x.Build + } + } + return 0 +} + +// Deprecated: Use GetBuild_ instead. +func (x *M3) GetBuild() int32 { + return x.GetBuild_() +} + +func (x *M3) GetProtoMessage() string { + if x != nil { + if x, ok := x.OneofField.(*M3_ProtoMessage_); ok { + return x.ProtoMessage_ + } + } + return "" +} + +// Deprecated: Use GetProtoMessage instead. +func (x *M3) GetProtoMessage_() string { + return x.GetProtoMessage() +} + +func (x *M3) GetReset() string { + if x != nil { + if x, ok := x.OneofField.(*M3_Reset_); ok { + return x.Reset_ + } + } + return "" +} + +// Deprecated: Use GetReset instead. +func (x *M3) GetReset_() string { + return x.GetReset() +} + +func (x *M3) GetString() string { + if x != nil { + if x, ok := x.OneofField.(*M3_String_); ok { + return x.String_ + } + } + return "" +} + +// Deprecated: Use GetString instead. +func (x *M3) GetString_() string { + return x.GetString() +} + +func (x *M3) GetDescriptor() string { + if x != nil { + if x, ok := x.OneofField.(*M3_Descriptor_); ok { + return x.Descriptor_ + } + } + return "" +} + +// Deprecated: Use GetDescriptor instead. +func (x *M3) GetDescriptor_() string { + return x.GetDescriptor() +} + +func (x *M3) GetSecondI32() int32 { + if x != nil { + return x.SecondI32 + } + return 0 +} + +func (x *M3) SetB(v bool) { + x.B = v +} + +func (x *M3) SetBytes(v []byte) { + if v == nil { + v = []byte{} + } + x.Bytes = v +} + +func (x *M3) SetF32(v float32) { + x.F32 = v +} + +func (x *M3) SetF64(v float64) { + x.F64 = v +} + +func (x *M3) SetI32(v int32) { + x.I32 = v +} + +func (x *M3) SetI64(v int64) { + x.I64 = v +} + +func (x *M3) SetUi32(v uint32) { + x.Ui32 = v +} + +func (x *M3) SetUi64(v uint64) { + x.Ui64 = v +} + +func (x *M3) SetS(v string) { + x.S = v +} + +func (x *M3) SetM(v *M3) { + x.M = v +} + +func (x *M3) SetIs(v []int32) { + x.Is = v +} + +func (x *M3) SetMs(v []*M3) { + x.Ms = v +} + +func (x *M3) SetMap(v map[string]bool) { + x.Map = v +} + +func (x *M3) SetE(v M3_Enum) { + x.E = v +} + +func (x *M3) SetStringOneof(v string) { + x.OneofField = &M3_StringOneof{v} +} + +func (x *M3) SetIntOneof(v int64) { + x.OneofField = &M3_IntOneof{v} +} + +func (x *M3) SetMsgOneof(v *M3) { + if v == nil { + x.OneofField = nil + return + } + x.OneofField = &M3_MsgOneof{v} +} + +func (x *M3) SetEnumOneof(v M3_Enum) { + x.OneofField = &M3_EnumOneof{v} +} + +func (x *M3) SetBytesOneof(v []byte) { + if v == nil { + v = []byte{} + } + x.OneofField = &M3_BytesOneof{v} +} + +func (x *M3) SetBuild_(v int32) { + x.OneofField = &M3_Build{v} +} + +func (x *M3) SetProtoMessage(v string) { + x.OneofField = &M3_ProtoMessage_{v} +} + +func (x *M3) SetReset(v string) { + x.OneofField = &M3_Reset_{v} +} + +func (x *M3) SetString(v string) { + x.OneofField = &M3_String_{v} +} + +func (x *M3) SetDescriptor(v string) { + x.OneofField = &M3_Descriptor_{v} +} + +func (x *M3) SetSecondI32(v int32) { + x.SecondI32 = v +} + +func (x *M3) HasM() bool { + if x == nil { + return false + } + return x.M != nil +} + +func (x *M3) HasOneofField() bool { + if x == nil { + return false + } + return x.OneofField != nil +} + +func (x *M3) HasStringOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_StringOneof) + return ok +} + +func (x *M3) HasIntOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_IntOneof) + return ok +} + +func (x *M3) HasMsgOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_MsgOneof) + return ok +} + +func (x *M3) HasEnumOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_EnumOneof) + return ok +} + +func (x *M3) HasBytesOneof() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_BytesOneof) + return ok +} + +func (x *M3) HasBuild_() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_Build) + return ok +} + +func (x *M3) HasProtoMessage() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_ProtoMessage_) + return ok +} + +func (x *M3) HasReset() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_Reset_) + return ok +} + +func (x *M3) HasString() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_String_) + return ok +} + +func (x *M3) HasDescriptor() bool { + if x == nil { + return false + } + _, ok := x.OneofField.(*M3_Descriptor_) + return ok +} + +func (x *M3) ClearM() { + x.M = nil +} + +func (x *M3) ClearOneofField() { + x.OneofField = nil +} + +func (x *M3) ClearStringOneof() { + if _, ok := x.OneofField.(*M3_StringOneof); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearIntOneof() { + if _, ok := x.OneofField.(*M3_IntOneof); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearMsgOneof() { + if _, ok := x.OneofField.(*M3_MsgOneof); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearEnumOneof() { + if _, ok := x.OneofField.(*M3_EnumOneof); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearBytesOneof() { + if _, ok := x.OneofField.(*M3_BytesOneof); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearBuild_() { + if _, ok := x.OneofField.(*M3_Build); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearProtoMessage() { + if _, ok := x.OneofField.(*M3_ProtoMessage_); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearReset() { + if _, ok := x.OneofField.(*M3_Reset_); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearString() { + if _, ok := x.OneofField.(*M3_String_); ok { + x.OneofField = nil + } +} + +func (x *M3) ClearDescriptor() { + if _, ok := x.OneofField.(*M3_Descriptor_); ok { + x.OneofField = nil + } +} + +const M3_OneofField_not_set_case case_M3_OneofField = 0 +const M3_StringOneof_case case_M3_OneofField = 14 +const M3_IntOneof_case case_M3_OneofField = 15 +const M3_MsgOneof_case case_M3_OneofField = 16 +const M3_EnumOneof_case case_M3_OneofField = 17 +const M3_BytesOneof_case case_M3_OneofField = 18 +const M3_Build_case case_M3_OneofField = 24 +const M3_ProtoMessage__case case_M3_OneofField = 25 +const M3_Reset__case case_M3_OneofField = 26 +const M3_String__case case_M3_OneofField = 27 +const M3_Descriptor__case case_M3_OneofField = 28 + +func (x *M3) WhichOneofField() case_M3_OneofField { + if x == nil { + return M3_OneofField_not_set_case + } + switch x.OneofField.(type) { + case *M3_StringOneof: + return M3_StringOneof_case + case *M3_IntOneof: + return M3_IntOneof_case + case *M3_MsgOneof: + return M3_MsgOneof_case + case *M3_EnumOneof: + return M3_EnumOneof_case + case *M3_BytesOneof: + return M3_BytesOneof_case + case *M3_Build: + return M3_Build_case + case *M3_ProtoMessage_: + return M3_ProtoMessage__case + case *M3_Reset_: + return M3_Reset__case + case *M3_String_: + return M3_String__case + case *M3_Descriptor_: + return M3_Descriptor__case + default: + return M3_OneofField_not_set_case + } +} + +type M3_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + B bool + Bytes []byte + F32 float32 + F64 float64 + I32 int32 + I64 int64 + Ui32 uint32 + Ui64 uint64 + S string + M *M3 + Is []int32 + Ms []*M3 + Map map[string]bool + E M3_Enum + // Fields of oneof OneofField: + StringOneof *string + IntOneof *int64 + MsgOneof *M3 + EnumOneof *M3_Enum + BytesOneof []byte + Build_ *int32 + ProtoMessage *string + Reset *string + String *string + Descriptor *string + // -- end of OneofField + SecondI32 int32 +} + +func (b0 M3_builder) Build() *M3 { + m0 := &M3{} + b, x := &b0, m0 + _, _ = b, x + x.B = b.B + x.Bytes = b.Bytes + x.F32 = b.F32 + x.F64 = b.F64 + x.I32 = b.I32 + x.I64 = b.I64 + x.Ui32 = b.Ui32 + x.Ui64 = b.Ui64 + x.S = b.S + x.M = b.M + x.Is = b.Is + x.Ms = b.Ms + x.Map = b.Map + x.E = b.E + if b.StringOneof != nil { + x.OneofField = &M3_StringOneof{*b.StringOneof} + } + if b.IntOneof != nil { + x.OneofField = &M3_IntOneof{*b.IntOneof} + } + if b.MsgOneof != nil { + x.OneofField = &M3_MsgOneof{b.MsgOneof} + } + if b.EnumOneof != nil { + x.OneofField = &M3_EnumOneof{*b.EnumOneof} + } + if b.BytesOneof != nil { + x.OneofField = &M3_BytesOneof{b.BytesOneof} + } + if b.Build_ != nil { + x.OneofField = &M3_Build{*b.Build_} + } + if b.ProtoMessage != nil { + x.OneofField = &M3_ProtoMessage_{*b.ProtoMessage} + } + if b.Reset != nil { + x.OneofField = &M3_Reset_{*b.Reset} + } + if b.String != nil { + x.OneofField = &M3_String_{*b.String} + } + if b.Descriptor != nil { + x.OneofField = &M3_Descriptor_{*b.Descriptor} + } + x.SecondI32 = b.SecondI32 + return m0 +} + +type case_M3_OneofField protoreflect.FieldNumber + +func (x case_M3_OneofField) String() string { + md := file_proto3test_proto_msgTypes[0].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isM3_OneofField interface { + isM3_OneofField() +} + +type M3_StringOneof struct { + StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,proto3,oneof"` +} + +type M3_IntOneof struct { + IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,proto3,oneof"` +} + +type M3_MsgOneof struct { + MsgOneof *M3 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,proto3,oneof"` +} + +type M3_EnumOneof struct { + EnumOneof M3_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum,oneof"` +} + +type M3_BytesOneof struct { + BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,proto3,oneof"` +} + +type M3_Build struct { + Build int32 `protobuf:"varint,24,opt,name=build,proto3,oneof"` +} + +type M3_ProtoMessage_ struct { + ProtoMessage_ string `protobuf:"bytes,25,opt,name=proto_message,json=protoMessage,proto3,oneof"` +} + +type M3_Reset_ struct { + Reset_ string `protobuf:"bytes,26,opt,name=reset,proto3,oneof"` +} + +type M3_String_ struct { + String_ string `protobuf:"bytes,27,opt,name=string,proto3,oneof"` +} + +type M3_Descriptor_ struct { + Descriptor_ string `protobuf:"bytes,28,opt,name=descriptor,proto3,oneof"` +} + +func (*M3_StringOneof) isM3_OneofField() {} + +func (*M3_IntOneof) isM3_OneofField() {} + +func (*M3_MsgOneof) isM3_OneofField() {} + +func (*M3_EnumOneof) isM3_OneofField() {} + +func (*M3_BytesOneof) isM3_OneofField() {} + +func (*M3_Build) isM3_OneofField() {} + +func (*M3_ProtoMessage_) isM3_OneofField() {} + +func (*M3_Reset_) isM3_OneofField() {} + +func (*M3_String_) isM3_OneofField() {} + +func (*M3_Descriptor_) isM3_OneofField() {} + +var File_proto3test_proto protoreflect.FileDescriptor + +var file_proto3test_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x23, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, + 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x22, 0xb0, 0x07, 0x0a, 0x02, 0x4d, 0x33, 0x12, 0x0c, + 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, + 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, + 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, + 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, + 0x12, 0x35, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x37, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x02, 0x6d, 0x73, + 0x12, 0x42, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x03, 0x6d, 0x61, 0x70, 0x12, 0x3a, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, + 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, + 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x46, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, + 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, + 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4d, 0x0a, 0x0a, + 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, + 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, + 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x16, + 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, + 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, + 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, + 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, + 0x1b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, + 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x49, 0x33, 0x32, + 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x42, 0x0d, 0x0a, 0x0b, 0x6f, + 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var file_proto3test_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_proto3test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto3test_proto_goTypes = []any{ + (M3_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test3.M3.Enum + (*M3)(nil), // 1: net.proto2.go.open2opaque.o2o.test3.M3 + nil, // 2: net.proto2.go.open2opaque.o2o.test3.M3.MapEntry +} +var file_proto3test_proto_depIdxs = []int32{ + 1, // 0: net.proto2.go.open2opaque.o2o.test3.M3.m:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 1, // 1: net.proto2.go.open2opaque.o2o.test3.M3.ms:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 2, // 2: net.proto2.go.open2opaque.o2o.test3.M3.map:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.MapEntry + 0, // 3: net.proto2.go.open2opaque.o2o.test3.M3.e:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum + 1, // 4: net.proto2.go.open2opaque.o2o.test3.M3.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 0, // 5: net.proto2.go.open2opaque.o2o.test3.M3.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_proto3test_proto_init() } +func file_proto3test_proto_init() { + if File_proto3test_proto != nil { + return + } + file_proto3test_proto_msgTypes[0].OneofWrappers = []any{ + (*M3_StringOneof)(nil), + (*M3_IntOneof)(nil), + (*M3_MsgOneof)(nil), + (*M3_EnumOneof)(nil), + (*M3_BytesOneof)(nil), + (*M3_Build)(nil), + (*M3_ProtoMessage_)(nil), + (*M3_Reset_)(nil), + (*M3_String_)(nil), + (*M3_Descriptor_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto3test_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto3test_proto_goTypes, + DependencyIndexes: file_proto3test_proto_depIdxs, + EnumInfos: file_proto3test_proto_enumTypes, + MessageInfos: file_proto3test_proto_msgTypes, + }.Build() + File_proto3test_proto = out.File + file_proto3test_proto_rawDesc = nil + file_proto3test_proto_goTypes = nil + file_proto3test_proto_depIdxs = nil +}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test.proto b/internal/fix/testdata/proto3test_go_proto/proto3test.proto new file mode 100644 index 0000000..414b4b6 --- /dev/null +++ b/internal/fix/testdata/proto3test_go_proto/proto3test.proto
@@ -0,0 +1,41 @@ +// 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. + +syntax = "proto3"; + +package net.proto2.go.open2opaque.o2o.test3; + +message M3 { + + bool b = 1; + bytes bytes = 2; + float f32 = 3; + double f64 = 4; + int32 i32 = 5; + int64 i64 = 6; + uint32 ui32 = 7; + uint64 ui64 = 8; + string s = 9; + M3 m = 10; + repeated int32 is = 11; + repeated M3 ms = 12; + map<string, bool> map = 29; + enum Enum { + E_VAL = 0; + } + Enum e = 13; + oneof oneof_field { + string string_oneof = 14; + int64 int_oneof = 15; + M3 msg_oneof = 16; + Enum enum_oneof = 17; + bytes bytes_oneof = 18; + int32 build = 24; + string proto_message = 25; + string reset = 26; + string string = 27; + string descriptor = 28; + } + int32 second_i32 = 30; +}
diff --git a/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go b/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go new file mode 100644 index 0000000..fef9755 --- /dev/null +++ b/internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go
@@ -0,0 +1,908 @@ +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2-devel +// protoc v5.29.1 +// source: proto3test.proto + +//go:build protoopaque + +package proto3test_go_proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type M3_Enum int32 + +const ( + M3_E_VAL M3_Enum = 0 +) + +// Enum value maps for M3_Enum. +var ( + M3_Enum_name = map[int32]string{ + 0: "E_VAL", + } + M3_Enum_value = map[string]int32{ + "E_VAL": 0, + } +) + +func (x M3_Enum) Enum() *M3_Enum { + p := new(M3_Enum) + *p = x + return p +} + +func (x M3_Enum) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (M3_Enum) Descriptor() protoreflect.EnumDescriptor { + return file_proto3test_proto_enumTypes[0].Descriptor() +} + +func (M3_Enum) Type() protoreflect.EnumType { + return &file_proto3test_proto_enumTypes[0] +} + +func (x M3_Enum) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +type M3 struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_B bool `protobuf:"varint,1,opt,name=b,proto3" json:"b,omitempty"` + xxx_hidden_Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"` + xxx_hidden_F32 float32 `protobuf:"fixed32,3,opt,name=f32,proto3" json:"f32,omitempty"` + xxx_hidden_F64 float64 `protobuf:"fixed64,4,opt,name=f64,proto3" json:"f64,omitempty"` + xxx_hidden_I32 int32 `protobuf:"varint,5,opt,name=i32,proto3" json:"i32,omitempty"` + xxx_hidden_I64 int64 `protobuf:"varint,6,opt,name=i64,proto3" json:"i64,omitempty"` + xxx_hidden_Ui32 uint32 `protobuf:"varint,7,opt,name=ui32,proto3" json:"ui32,omitempty"` + xxx_hidden_Ui64 uint64 `protobuf:"varint,8,opt,name=ui64,proto3" json:"ui64,omitempty"` + xxx_hidden_S string `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` + xxx_hidden_M *M3 `protobuf:"bytes,10,opt,name=m,proto3" json:"m,omitempty"` + xxx_hidden_Is []int32 `protobuf:"varint,11,rep,packed,name=is,proto3" json:"is,omitempty"` + xxx_hidden_Ms *[]*M3 `protobuf:"bytes,12,rep,name=ms,proto3" json:"ms,omitempty"` + xxx_hidden_Map map[string]bool `protobuf:"bytes,29,rep,name=map,proto3" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + xxx_hidden_E M3_Enum `protobuf:"varint,13,opt,name=e,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum" json:"e,omitempty"` + xxx_hidden_OneofField isM3_OneofField `protobuf_oneof:"oneof_field"` + xxx_hidden_SecondI32 int32 `protobuf:"varint,30,opt,name=second_i32,json=secondI32,proto3" json:"second_i32,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *M3) Reset() { + *x = M3{} + mi := &file_proto3test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *M3) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*M3) ProtoMessage() {} + +func (x *M3) ProtoReflect() protoreflect.Message { + mi := &file_proto3test_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *M3) GetB() bool { + if x != nil { + return x.xxx_hidden_B + } + return false +} + +func (x *M3) GetBytes() []byte { + if x != nil { + return x.xxx_hidden_Bytes + } + return nil +} + +func (x *M3) GetF32() float32 { + if x != nil { + return x.xxx_hidden_F32 + } + return 0 +} + +func (x *M3) GetF64() float64 { + if x != nil { + return x.xxx_hidden_F64 + } + return 0 +} + +func (x *M3) GetI32() int32 { + if x != nil { + return x.xxx_hidden_I32 + } + return 0 +} + +func (x *M3) GetI64() int64 { + if x != nil { + return x.xxx_hidden_I64 + } + return 0 +} + +func (x *M3) GetUi32() uint32 { + if x != nil { + return x.xxx_hidden_Ui32 + } + return 0 +} + +func (x *M3) GetUi64() uint64 { + if x != nil { + return x.xxx_hidden_Ui64 + } + return 0 +} + +func (x *M3) GetS() string { + if x != nil { + return x.xxx_hidden_S + } + return "" +} + +func (x *M3) GetM() *M3 { + if x != nil { + return x.xxx_hidden_M + } + return nil +} + +func (x *M3) GetIs() []int32 { + if x != nil { + return x.xxx_hidden_Is + } + return nil +} + +func (x *M3) GetMs() []*M3 { + if x != nil { + if x.xxx_hidden_Ms != nil { + return *x.xxx_hidden_Ms + } + } + return nil +} + +func (x *M3) GetMap() map[string]bool { + if x != nil { + return x.xxx_hidden_Map + } + return nil +} + +func (x *M3) GetE() M3_Enum { + if x != nil { + return x.xxx_hidden_E + } + return M3_E_VAL +} + +func (x *M3) GetStringOneof() string { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_StringOneof); ok { + return x.StringOneof + } + } + return "" +} + +func (x *M3) GetIntOneof() int64 { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_IntOneof); ok { + return x.IntOneof + } + } + return 0 +} + +func (x *M3) GetMsgOneof() *M3 { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof); ok { + return x.MsgOneof + } + } + return nil +} + +func (x *M3) GetEnumOneof() M3_Enum { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof); ok { + return x.EnumOneof + } + } + return M3_E_VAL +} + +func (x *M3) GetBytesOneof() []byte { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof); ok { + return x.BytesOneof + } + } + return nil +} + +func (x *M3) GetBuild_() int32 { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_Build); ok { + return x.Build + } + } + return 0 +} + +func (x *M3) GetProtoMessage() string { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_); ok { + return x.ProtoMessage_ + } + } + return "" +} + +func (x *M3) GetReset() string { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_Reset_); ok { + return x.Reset_ + } + } + return "" +} + +func (x *M3) GetString() string { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_String_); ok { + return x.String_ + } + } + return "" +} + +func (x *M3) GetDescriptor() string { + if x != nil { + if x, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_); ok { + return x.Descriptor_ + } + } + return "" +} + +func (x *M3) GetSecondI32() int32 { + if x != nil { + return x.xxx_hidden_SecondI32 + } + return 0 +} + +func (x *M3) SetB(v bool) { + x.xxx_hidden_B = v +} + +func (x *M3) SetBytes(v []byte) { + if v == nil { + v = []byte{} + } + x.xxx_hidden_Bytes = v +} + +func (x *M3) SetF32(v float32) { + x.xxx_hidden_F32 = v +} + +func (x *M3) SetF64(v float64) { + x.xxx_hidden_F64 = v +} + +func (x *M3) SetI32(v int32) { + x.xxx_hidden_I32 = v +} + +func (x *M3) SetI64(v int64) { + x.xxx_hidden_I64 = v +} + +func (x *M3) SetUi32(v uint32) { + x.xxx_hidden_Ui32 = v +} + +func (x *M3) SetUi64(v uint64) { + x.xxx_hidden_Ui64 = v +} + +func (x *M3) SetS(v string) { + x.xxx_hidden_S = v +} + +func (x *M3) SetM(v *M3) { + x.xxx_hidden_M = v +} + +func (x *M3) SetIs(v []int32) { + x.xxx_hidden_Is = v +} + +func (x *M3) SetMs(v []*M3) { + x.xxx_hidden_Ms = &v +} + +func (x *M3) SetMap(v map[string]bool) { + x.xxx_hidden_Map = v +} + +func (x *M3) SetE(v M3_Enum) { + x.xxx_hidden_E = v +} + +func (x *M3) SetStringOneof(v string) { + x.xxx_hidden_OneofField = &m3_StringOneof{v} +} + +func (x *M3) SetIntOneof(v int64) { + x.xxx_hidden_OneofField = &m3_IntOneof{v} +} + +func (x *M3) SetMsgOneof(v *M3) { + if v == nil { + x.xxx_hidden_OneofField = nil + return + } + x.xxx_hidden_OneofField = &m3_MsgOneof{v} +} + +func (x *M3) SetEnumOneof(v M3_Enum) { + x.xxx_hidden_OneofField = &m3_EnumOneof{v} +} + +func (x *M3) SetBytesOneof(v []byte) { + if v == nil { + v = []byte{} + } + x.xxx_hidden_OneofField = &m3_BytesOneof{v} +} + +func (x *M3) SetBuild_(v int32) { + x.xxx_hidden_OneofField = &m3_Build{v} +} + +func (x *M3) SetProtoMessage(v string) { + x.xxx_hidden_OneofField = &m3_ProtoMessage_{v} +} + +func (x *M3) SetReset(v string) { + x.xxx_hidden_OneofField = &m3_Reset_{v} +} + +func (x *M3) SetString(v string) { + x.xxx_hidden_OneofField = &m3_String_{v} +} + +func (x *M3) SetDescriptor(v string) { + x.xxx_hidden_OneofField = &m3_Descriptor_{v} +} + +func (x *M3) SetSecondI32(v int32) { + x.xxx_hidden_SecondI32 = v +} + +func (x *M3) HasM() bool { + if x == nil { + return false + } + return x.xxx_hidden_M != nil +} + +func (x *M3) HasOneofField() bool { + if x == nil { + return false + } + return x.xxx_hidden_OneofField != nil +} + +func (x *M3) HasStringOneof() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_StringOneof) + return ok +} + +func (x *M3) HasIntOneof() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_IntOneof) + return ok +} + +func (x *M3) HasMsgOneof() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof) + return ok +} + +func (x *M3) HasEnumOneof() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof) + return ok +} + +func (x *M3) HasBytesOneof() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof) + return ok +} + +func (x *M3) HasBuild_() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_Build) + return ok +} + +func (x *M3) HasProtoMessage() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_) + return ok +} + +func (x *M3) HasReset() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_Reset_) + return ok +} + +func (x *M3) HasString() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_String_) + return ok +} + +func (x *M3) HasDescriptor() bool { + if x == nil { + return false + } + _, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_) + return ok +} + +func (x *M3) ClearM() { + x.xxx_hidden_M = nil +} + +func (x *M3) ClearOneofField() { + x.xxx_hidden_OneofField = nil +} + +func (x *M3) ClearStringOneof() { + if _, ok := x.xxx_hidden_OneofField.(*m3_StringOneof); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearIntOneof() { + if _, ok := x.xxx_hidden_OneofField.(*m3_IntOneof); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearMsgOneof() { + if _, ok := x.xxx_hidden_OneofField.(*m3_MsgOneof); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearEnumOneof() { + if _, ok := x.xxx_hidden_OneofField.(*m3_EnumOneof); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearBytesOneof() { + if _, ok := x.xxx_hidden_OneofField.(*m3_BytesOneof); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearBuild_() { + if _, ok := x.xxx_hidden_OneofField.(*m3_Build); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearProtoMessage() { + if _, ok := x.xxx_hidden_OneofField.(*m3_ProtoMessage_); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearReset() { + if _, ok := x.xxx_hidden_OneofField.(*m3_Reset_); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearString() { + if _, ok := x.xxx_hidden_OneofField.(*m3_String_); ok { + x.xxx_hidden_OneofField = nil + } +} + +func (x *M3) ClearDescriptor() { + if _, ok := x.xxx_hidden_OneofField.(*m3_Descriptor_); ok { + x.xxx_hidden_OneofField = nil + } +} + +const M3_OneofField_not_set_case case_M3_OneofField = 0 +const M3_StringOneof_case case_M3_OneofField = 14 +const M3_IntOneof_case case_M3_OneofField = 15 +const M3_MsgOneof_case case_M3_OneofField = 16 +const M3_EnumOneof_case case_M3_OneofField = 17 +const M3_BytesOneof_case case_M3_OneofField = 18 +const M3_Build_case case_M3_OneofField = 24 +const M3_ProtoMessage__case case_M3_OneofField = 25 +const M3_Reset__case case_M3_OneofField = 26 +const M3_String__case case_M3_OneofField = 27 +const M3_Descriptor__case case_M3_OneofField = 28 + +func (x *M3) WhichOneofField() case_M3_OneofField { + if x == nil { + return M3_OneofField_not_set_case + } + switch x.xxx_hidden_OneofField.(type) { + case *m3_StringOneof: + return M3_StringOneof_case + case *m3_IntOneof: + return M3_IntOneof_case + case *m3_MsgOneof: + return M3_MsgOneof_case + case *m3_EnumOneof: + return M3_EnumOneof_case + case *m3_BytesOneof: + return M3_BytesOneof_case + case *m3_Build: + return M3_Build_case + case *m3_ProtoMessage_: + return M3_ProtoMessage__case + case *m3_Reset_: + return M3_Reset__case + case *m3_String_: + return M3_String__case + case *m3_Descriptor_: + return M3_Descriptor__case + default: + return M3_OneofField_not_set_case + } +} + +type M3_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + B bool + Bytes []byte + F32 float32 + F64 float64 + I32 int32 + I64 int64 + Ui32 uint32 + Ui64 uint64 + S string + M *M3 + Is []int32 + Ms []*M3 + Map map[string]bool + E M3_Enum + // Fields of oneof xxx_hidden_OneofField: + StringOneof *string + IntOneof *int64 + MsgOneof *M3 + EnumOneof *M3_Enum + BytesOneof []byte + Build_ *int32 + ProtoMessage *string + Reset *string + String *string + Descriptor *string + // -- end of xxx_hidden_OneofField + SecondI32 int32 +} + +func (b0 M3_builder) Build() *M3 { + m0 := &M3{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_B = b.B + x.xxx_hidden_Bytes = b.Bytes + x.xxx_hidden_F32 = b.F32 + x.xxx_hidden_F64 = b.F64 + x.xxx_hidden_I32 = b.I32 + x.xxx_hidden_I64 = b.I64 + x.xxx_hidden_Ui32 = b.Ui32 + x.xxx_hidden_Ui64 = b.Ui64 + x.xxx_hidden_S = b.S + x.xxx_hidden_M = b.M + x.xxx_hidden_Is = b.Is + x.xxx_hidden_Ms = &b.Ms + x.xxx_hidden_Map = b.Map + x.xxx_hidden_E = b.E + if b.StringOneof != nil { + x.xxx_hidden_OneofField = &m3_StringOneof{*b.StringOneof} + } + if b.IntOneof != nil { + x.xxx_hidden_OneofField = &m3_IntOneof{*b.IntOneof} + } + if b.MsgOneof != nil { + x.xxx_hidden_OneofField = &m3_MsgOneof{b.MsgOneof} + } + if b.EnumOneof != nil { + x.xxx_hidden_OneofField = &m3_EnumOneof{*b.EnumOneof} + } + if b.BytesOneof != nil { + x.xxx_hidden_OneofField = &m3_BytesOneof{b.BytesOneof} + } + if b.Build_ != nil { + x.xxx_hidden_OneofField = &m3_Build{*b.Build_} + } + if b.ProtoMessage != nil { + x.xxx_hidden_OneofField = &m3_ProtoMessage_{*b.ProtoMessage} + } + if b.Reset != nil { + x.xxx_hidden_OneofField = &m3_Reset_{*b.Reset} + } + if b.String != nil { + x.xxx_hidden_OneofField = &m3_String_{*b.String} + } + if b.Descriptor != nil { + x.xxx_hidden_OneofField = &m3_Descriptor_{*b.Descriptor} + } + x.xxx_hidden_SecondI32 = b.SecondI32 + return m0 +} + +type case_M3_OneofField protoreflect.FieldNumber + +func (x case_M3_OneofField) String() string { + md := file_proto3test_proto_msgTypes[0].Descriptor() + if x == 0 { + return "not set" + } + return protoimpl.X.MessageFieldStringOf(md, protoreflect.FieldNumber(x)) +} + +type isM3_OneofField interface { + isM3_OneofField() +} + +type m3_StringOneof struct { + StringOneof string `protobuf:"bytes,14,opt,name=string_oneof,json=stringOneof,proto3,oneof"` +} + +type m3_IntOneof struct { + IntOneof int64 `protobuf:"varint,15,opt,name=int_oneof,json=intOneof,proto3,oneof"` +} + +type m3_MsgOneof struct { + MsgOneof *M3 `protobuf:"bytes,16,opt,name=msg_oneof,json=msgOneof,proto3,oneof"` +} + +type m3_EnumOneof struct { + EnumOneof M3_Enum `protobuf:"varint,17,opt,name=enum_oneof,json=enumOneof,proto3,enum=net.proto2.go.open2opaque.o2o.test3.M3_Enum,oneof"` +} + +type m3_BytesOneof struct { + BytesOneof []byte `protobuf:"bytes,18,opt,name=bytes_oneof,json=bytesOneof,proto3,oneof"` +} + +type m3_Build struct { + Build int32 `protobuf:"varint,24,opt,name=build,proto3,oneof"` +} + +type m3_ProtoMessage_ struct { + ProtoMessage_ string `protobuf:"bytes,25,opt,name=proto_message,json=protoMessage,proto3,oneof"` +} + +type m3_Reset_ struct { + Reset_ string `protobuf:"bytes,26,opt,name=reset,proto3,oneof"` +} + +type m3_String_ struct { + String_ string `protobuf:"bytes,27,opt,name=string,proto3,oneof"` +} + +type m3_Descriptor_ struct { + Descriptor_ string `protobuf:"bytes,28,opt,name=descriptor,proto3,oneof"` +} + +func (*m3_StringOneof) isM3_OneofField() {} + +func (*m3_IntOneof) isM3_OneofField() {} + +func (*m3_MsgOneof) isM3_OneofField() {} + +func (*m3_EnumOneof) isM3_OneofField() {} + +func (*m3_BytesOneof) isM3_OneofField() {} + +func (*m3_Build) isM3_OneofField() {} + +func (*m3_ProtoMessage_) isM3_OneofField() {} + +func (*m3_Reset_) isM3_OneofField() {} + +func (*m3_String_) isM3_OneofField() {} + +func (*m3_Descriptor_) isM3_OneofField() {} + +var File_proto3test_proto protoreflect.FileDescriptor + +var file_proto3test_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x23, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, + 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, + 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x22, 0xb0, 0x07, 0x0a, 0x02, 0x4d, 0x33, 0x12, 0x0c, + 0x0a, 0x01, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x14, 0x0a, 0x05, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, + 0x03, 0x66, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x03, 0x66, 0x36, 0x34, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x69, + 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x75, 0x69, 0x33, 0x32, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x69, 0x36, 0x34, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x75, 0x69, + 0x36, 0x34, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x73, + 0x12, 0x35, 0x0a, 0x01, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, + 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x01, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x05, 0x52, 0x02, 0x69, 0x73, 0x12, 0x37, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x0c, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, + 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, + 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x52, 0x02, 0x6d, 0x73, + 0x12, 0x42, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x1d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, + 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, + 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x03, 0x6d, 0x61, 0x70, 0x12, 0x3a, 0x0a, 0x01, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, + 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x01, 0x65, + 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x4f, + 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x46, 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, + 0x66, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, + 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, + 0x48, 0x00, 0x52, 0x08, 0x6d, 0x73, 0x67, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x4d, 0x0a, 0x0a, + 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x2c, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x2e, 0x67, 0x6f, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x32, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x2e, 0x6f, 0x32, 0x6f, + 0x2e, 0x74, 0x65, 0x73, 0x74, 0x33, 0x2e, 0x4d, 0x33, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, + 0x52, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x21, 0x0a, 0x0b, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, + 0x48, 0x00, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x16, + 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, + 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x25, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, + 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, + 0x72, 0x65, 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, + 0x1b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, + 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x49, 0x33, 0x32, + 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x11, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x10, 0x00, 0x42, 0x0d, 0x0a, 0x0b, 0x6f, + 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var file_proto3test_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_proto3test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto3test_proto_goTypes = []any{ + (M3_Enum)(0), // 0: net.proto2.go.open2opaque.o2o.test3.M3.Enum + (*M3)(nil), // 1: net.proto2.go.open2opaque.o2o.test3.M3 + nil, // 2: net.proto2.go.open2opaque.o2o.test3.M3.MapEntry +} +var file_proto3test_proto_depIdxs = []int32{ + 1, // 0: net.proto2.go.open2opaque.o2o.test3.M3.m:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 1, // 1: net.proto2.go.open2opaque.o2o.test3.M3.ms:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 2, // 2: net.proto2.go.open2opaque.o2o.test3.M3.map:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.MapEntry + 0, // 3: net.proto2.go.open2opaque.o2o.test3.M3.e:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum + 1, // 4: net.proto2.go.open2opaque.o2o.test3.M3.msg_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3 + 0, // 5: net.proto2.go.open2opaque.o2o.test3.M3.enum_oneof:type_name -> net.proto2.go.open2opaque.o2o.test3.M3.Enum + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_proto3test_proto_init() } +func file_proto3test_proto_init() { + if File_proto3test_proto != nil { + return + } + file_proto3test_proto_msgTypes[0].OneofWrappers = []any{ + (*m3_StringOneof)(nil), + (*m3_IntOneof)(nil), + (*m3_MsgOneof)(nil), + (*m3_EnumOneof)(nil), + (*m3_BytesOneof)(nil), + (*m3_Build)(nil), + (*m3_ProtoMessage_)(nil), + (*m3_Reset_)(nil), + (*m3_String_)(nil), + (*m3_Descriptor_)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto3test_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto3test_proto_goTypes, + DependencyIndexes: file_proto3test_proto_depIdxs, + EnumInfos: file_proto3test_proto_enumTypes, + MessageInfos: file_proto3test_proto_msgTypes, + }.Build() + File_proto3test_proto = out.File + file_proto3test_proto_rawDesc = nil + file_proto3test_proto_goTypes = nil + file_proto3test_proto_depIdxs = nil +}
diff --git a/internal/fix/usepointers.go b/internal/fix/usepointers.go new file mode 100644 index 0000000..987a325 --- /dev/null +++ b/internal/fix/usepointers.go
@@ -0,0 +1,218 @@ +// 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 fix + +import ( + "go/token" + "go/types" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" + "google.golang.org/open2opaque/internal/protodetecttypes" +) + +// usePointersPre replaces usage of Go proto structs as values with pointer to structs (i.e. the +// only correct way to handle protos). +// +// NOTE: usePointersPre is called (once) for the *dst.File. Recursion aborts +// because it returns false; the dstutil.Apply() calls within the function +// traverse the entire file. +func usePointersPre(c *cursor) bool { + // The rewrite below is only for the Red level. + if c.lvl.le(Yellow) { + return false + } + + // x := pb.M{} => x := &pb.M{} + // x := pb.M{F: V} => x := &pb.M{F: V} + // This is ok if x is never copied (shallow copy). + // It could be ok in other cases too but we implement the common case for now. + // We only deal with the short-form definition for now (the long form hasn't showed up in practice). + dstutil.Apply(c.Node(), func(cur *dstutil.Cursor) bool { + switch n := cur.Node().(type) { + case *dst.BlockStmt: + for j, stmt := range n.List { + ident, ok := nonPtrProtoStructDef(c, stmt) + if !ok { + continue + } + if replaceWithPtr(c, ident, n.List[j+1:]) { + c.setType(ident, types.NewPointer(c.typeOf(ident))) + a := stmt.(*dst.AssignStmt) + a.Rhs[0] = addr(c, a.Rhs[0]) + c.numUnsafeRewritesByReason[PotentialBuildBreakage]++ + } + } + + case *dst.Field: + T := c.typeOf(n.Type) + _, alreadyStar := n.Type.(*dst.StarExpr) + if (protodetecttypes.Type{T: T}).IsMessage() && !alreadyStar { + sexpr := &dst.StarExpr{X: n.Type} + c.setType(sexpr, types.NewPointer(T)) + n.Type = sexpr + c.numUnsafeRewritesByReason[PotentialBuildBreakage]++ + } + + } + return true + }, nil) + + // Rewrite all non-pointer composite literals, even though it breaks + // compilation. We annotate these rewrites with a FIXME comment. It is up to + // the user to change the usage of this literal to cope with it now being a + // pointer. + dstutil.Apply(c.Node(), func(cur *dstutil.Cursor) bool { + lit, ok := cur.Node().(*dst.CompositeLit) + if !ok { + return true + } + if isAddr(cur.Parent()) { + // The code already takes the address of this composite literal + // (e.g. &pb.M2{literal}), resulting in a pointer. Skip. + return true + } + + typ := c.typeOf(lit) + if _, ok := typ.Underlying().(*types.Pointer); ok { + // The composite literal implicitly is already a pointer type + // (e.g. []*pb.M2{{literal}}). Skip. + return true + } + + if !c.shouldUpdateType(typ) { + return true + } + + addCommentAbove(c.Node(), lit, "// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value)") + + c.numUnsafeRewritesByReason[IncompleteRewrite]++ + cur.Replace(addr(c, lit)) + + return true + }, nil) + return false +} + +// nonPtrProtoStructDef returns true if stmt is of the form: +// +// x := pb.M{...} +// +// It also returns the assigned identifier ('x' in this example) if that's the case. +func nonPtrProtoStructDef(c *cursor, stmt dst.Stmt) (*dst.Ident, bool) { + // Not handled: multi-assign + a, ok := stmt.(*dst.AssignStmt) + if !ok || a.Tok != token.DEFINE || len(a.Lhs) != 1 { + return nil, false + } + lit, ok := a.Rhs[0].(*dst.CompositeLit) + if !ok { + return nil, false + } + ident, ok := a.Lhs[0].(*dst.Ident) + if !ok { + // Shouldn't happen as only identifiers can be on LHS of a definition. + return nil, false + } + if !c.shouldUpdateType(c.typeOf(lit)) { + return nil, false + } + return ident, true +} + +// replaceWithPtr modifies (if possible) stmts so that they work if ident was a pointer type (it +// must be a non-pointer). For example, for identifier 'x' the following statements: +// +// x.S = nil +// _ = x.GetS() +// f(&x) +// +// would be changed to: +// +// x.S = nil +// _ = x.GetS() +// f(x) // notice dropped "&" +// +// Changes are only made if they are possible. Any usage of the identifier for shallow copies +// prevents rewrites. For example, in the following example statements won't be changed: +// +// f(&x) +// E +// +// where E is any of +// +// x = pb.M{} +// y = x +// *p = x +// f(x) +// []pb.M{x} +// +// or anything else that's not a field access, a method call, &x, or an argument to a printer function. +// +// replaceWithPtr returns true if it modifies stmts. +func replaceWithPtr(c *cursor, ident *dst.Ident, stmts []dst.Stmt) bool { + b := &dst.BlockStmt{List: stmts} + canRewrite := true + dstutil.Apply(b, func(cur *dstutil.Cursor) bool { + n, ok := cur.Node().(*dst.Ident) + if !ok { + return true + } + if c.objectOf(n) != c.objectOf(ident) { + return false + } + if isAddr(cur.Parent()) { + return false + } + + // If n's parent is a selector expression then we have one of those situations: + // + // n.Field + // n.Func() + // + // This works whether n is a pointer or a value. + if _, ok := cur.Parent().(*dst.SelectorExpr); ok { + return false + } + + // Address expressions are ok. We have to drop "&" once ident becomes a pointer. + if e, ok := cur.Parent().(*dst.UnaryExpr); ok && e.Op == token.AND { + return false + } + + // If n is an argument to t.Errorf or other function used for printing then it's ok to make + // n a pointer. That will change output format from: + // {F:...} + // to + // &{F:...} + // A shallow copy like this is incorrect. It's rare to depend on exact log statement though (I'm sure it happens). + if c.lvl.ge(Yellow) && c.looksLikePrintf(cur.Parent()) { + return false + } + + // Otherwise we found a use of ident that can't be changed to a pointer. + canRewrite = false + + return false + }, nil) + if !canRewrite { + return false + } + + // We can change the type of ident to be a pointer. We must replace all "&ident" expressions with "ident". + dstutil.Apply(b, func(cur *dstutil.Cursor) bool { + ue, ok := cur.Node().(*dst.UnaryExpr) + if !ok || ue.Op != token.AND { + return true + } + v, ok := ue.X.(*dst.Ident) + if !ok || c.objectOf(ident) != c.objectOf(v) { + return true + } + cur.Replace(v) + return false + }, nil) + return true +}
diff --git a/internal/fix/usepointers_test.go b/internal/fix/usepointers_test.go new file mode 100644 index 0000000..5d1e757 --- /dev/null +++ b/internal/fix/usepointers_test.go
@@ -0,0 +1,548 @@ +// 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 fix + +import ( + "testing" +) + +func TestEnumPointer(t *testing.T) { + tests := []test{ + { + desc: "enum (value)", + srcfiles: []string{"pkg.go"}, + in: ` +mypb := &pb2.M2{ + E: pb2.M2_E_VAL.Enum(), +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +mypb := &pb2.M2{} +mypb.SetE(pb2.M2_E_VAL) +_ = mypb +`, + }, + }, + + { + desc: "enum (pointer)", + srcfiles: []string{"pkg.go"}, + in: ` +ptr := pb2.M2_E_VAL.Enum() +mypb := &pb2.M2{ + E: ptr, +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +ptr := pb2.M2_E_VAL.Enum() +mypb := &pb2.M2{} +if ptr != nil { + mypb.SetE(*ptr) +} +_ = mypb +`, + }, + }, + + { + desc: "enum (Enum() on pointer)", + srcfiles: []string{"pkg.go"}, + in: ` +ptr := pb2.M2_E_VAL.Enum() +mypb := &pb2.M2{ + E: ptr.Enum(), +} +_ = mypb +`, + want: map[Level]string{ + Green: ` +ptr := pb2.M2_E_VAL.Enum() +mypb := &pb2.M2{} +mypb.SetE(*ptr) +_ = mypb +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestUsePointersNotValues(t *testing.T) { + tests := []test{ + { + desc: "simple example", + extra: `func f(*pb2.M2) {}`, + in: ` +m := pb2.M2{S:nil} +f(&m) +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +f(&m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +f(m) +`, + }, + }, + + { + desc: "empty literal", + in: ` +m := pb2.M2{} +_ = &m +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{} +_ = &m +`, + Red: ` +m := &pb2.M2{} +_ = m +`, + }, + }, + + { + desc: "method calls", + extra: `func f(*pb2.M2) {}`, + in: ` +m := pb2.M2{S:nil} +_ = m.GetS() +f(&m) +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +_ = m.GetS() +f(&m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +_ = m.GetS() +f(m) +`, + }, + }, + + { + desc: "direct field accesses", + extra: `func f(*pb2.M2) {}`, + in: ` +m := pb2.M2{S: nil} +m.S = proto.String("") +m.S = nil +f(&m) +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +m.SetS("") +m.ClearS() +f(&m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +m.SetS("") +m.ClearS() +f(m) +`, + }, + }, + + { + desc: "addr is stored", + in: ` +m := pb2.M2{S: nil} +m.M = &m +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +m.SetM(&m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +m.SetM(m) +`, + }, + }, + + { + desc: "argument to function printer", + extra: `func fmtPrintln(string, ...interface{})`, + in: ` +m := pb2.M2{S: nil} +fmtPrintln("", m) +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +fmtPrintln("", m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +fmtPrintln("", m) +`, + }, + }, + + { + desc: "argument to method printer", + extra: ` +type T struct{} +func (*T) println(string, ...interface{}) +var t *T +`, + in: ` +m := pb2.M2{S: nil} +t.println("", m) +`, + want: map[Level]string{ + Green: ` +m := pb2.M2{S: nil} +t.println("", m) +`, + Red: ` +m := pb2.M2_builder{S: nil}.Build() +t.println("", m) +`, + }, + }, + + { + desc: "array of values", + in: ` +ms := []pb2.M2{{S: nil}, pb2.M2{S: nil}, {}} +_ = ms +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +ms := []pb2.M2{&{S: nil}, pb2.M2_builder{S: nil}.Build(), &{}} +_ = ms +`, + }, + }, + + { + desc: "array value", + extra: `func f(*pb2.M2) {}`, + in: ` +m := pb2.M2{} +ms := []pb2.M2{m} +f(&m) +_ = ms +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +m := &pb2.M2{} +ms := []pb2.M2{m} +f(&m) +_ = ms +`, + }, + }, + + { + desc: "shallow copy, func call", + extra: ` +func f(*pb2.M2) {} +func g(pb2.M2) {}`, + in: ` +m := pb2.M2{S: nil} +f(&m) +g(m) +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +m := pb2.M2_builder{S: nil}.Build() +f(&m) +g(m) +`, + }, + }, + + { + desc: "shallow copy, return value", + extra: ` +func f(*pb2.M2) {} +func g(pb2.M2) {}`, + in: ` +m := func() pb2.M2 { + return pb2.M2{S: nil} +}() +f(&m) +g(m) +`, + want: map[Level]string{ + Red: ` +m := func() *pb2.M2 { + // DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) + return pb2.M2_builder{S: nil}.Build() +}() +f(&m) +g(m) +`, + }, + }, + + { + desc: "shallow copy, output arg", + extra: `func f(*pb2.M2) {}`, + in: ` +func(out *pb2.M2) { + m := pb2.M2{S: nil} + f(&m) + *out = m +}(m2) +`, + want: map[Level]string{ + Red: ` +func(out *pb2.M2) { + m := pb2.M2_builder{S: nil}.Build() + f(m) + proto.Reset(out) + proto.Merge(out, m) +}(m2) +`, + }, + }, + + { + desc: "shallow copy, direct assignment to output arg", + srcfiles: []string{"pkg.go"}, + in: ` +var out *pb2.M2 +*out = pb2.M2{S: nil} +`, + want: map[Level]string{ + Green: ` +var out *pb2.M2 +proto.Reset(out) +m2h2 := &pb2.M2{} +m2h2.ClearS() +proto.Merge(out, m2h2) +`, + }, + }, + + { + desc: "shallow copy, conditional assignment to output arg", + srcfiles: []string{"pkg.go"}, + in: ` +var out *pb2.M2 +if out != nil { + *out = pb2.M2{S: nil} +} +`, + want: map[Level]string{ + Green: ` +var out *pb2.M2 +if out != nil { + proto.Reset(out) + m2h2 := &pb2.M2{} + m2h2.ClearS() + proto.Merge(out, m2h2) +} +`, + }, + }, + + { + desc: "shallow copy, simple copy", + extra: `func f(*pb2.M2) {}`, + in: ` +m := pb2.M2{S: nil} +var copy pb2.M2 = m +_ = copy +f(&m) +f(©) +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +m := pb2.M2_builder{S: nil}.Build() +var copy pb2.M2 = m +_ = copy +f(&m) +f(©) +`, + }, + }, + + { + desc: "shallow copy, reassigned", + extra: ` +func f(*pb2.M2) { } +func g() pb2.M2{ return pb2.M2{} } +`, + in: ` +// existing comment to illustrate comment addition +m := pb2.M2{S: nil} +m = g() +f(&m) +`, + want: map[Level]string{ + Red: ` +// existing comment to illustrate comment addition +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +m := pb2.M2_builder{S: nil}.Build() +m = g() +f(&m) +`, + }, + }, + + { + desc: "struct field definition", + in: ` +for _, tt := range []struct { + want pb2.M2 +}{ + { + want: pb2.M2{S: nil}, + }, +} { + _ = tt.want +} +`, + want: map[Level]string{ + Red: ` +// DO NOT SUBMIT: fix callers to work with a pointer (go/goprotoapi-findings#message-value) +for _, tt := range []struct { + want *pb2.M2 +}{ + { + want: pb2.M2_builder{S: nil}.Build(), + }, +} { + _ = tt.want +} +`, + }, + }, + + { + desc: "Stubby method handler response assignment", + in: ` +func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error { + *resp = pb2.M2{S: nil} + return nil +}(context.Background(), nil, nil) +`, + want: map[Level]string{ + Red: ` +func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error { + proto.Merge(resp, pb2.M2_builder{S: nil}.Build()) + return nil +}(context.Background(), nil, nil) +`, + }, + }, + + { + desc: "Stubby method handler, mutating response assignment", + in: ` +func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error { + *resp = pb2.M2{I32: proto.Int32(42)} + if true { + *resp = pb2.M2{S: nil} + } + return nil +}(context.Background(), nil, nil) +`, + want: map[Level]string{ + Red: ` +func(ctx context.Context, req *pb2.M2, resp *pb2.M2) error { + proto.Merge(resp, pb2.M2_builder{I32: proto.Int32(42)}.Build()) + if true { + proto.Reset(resp) + proto.Merge(resp, pb2.M2_builder{S: nil}.Build()) + } + return nil +}(context.Background(), nil, nil) +`, + }, + }, + + { + desc: "Stubby method handler response assignment with comments", + in: ` +var response *pb2.M2 +// above response assignment +*response = pb2.M2{ + // above field + S: nil, // end of field line +} // end of message line +`, + want: map[Level]string{ + Red: ` +var response *pb2.M2 +// above response assignment +proto.Reset(response) +proto.Merge(response, pb2.M2_builder{ + // above field + S: nil, // end of field line +}.Build()) // end of message line +`, + }, + }, + } + + runTableTests(t, tests) +} + +func TestNoUnconditionalDereference(t *testing.T) { + // Before b/259702553, the open2opaque tool unconditionally rewrote code + // such that it de-referenced pointers. + + tests := []test{ + { + desc: "assignment", + extra: `func funcReturningIntPointer() *int32 { return nil }`, + in: ` +m2.I32 = funcReturningIntPointer() +`, + want: map[Level]string{ + Red: ` +if x := funcReturningIntPointer(); x != nil { + m2.SetI32(*x) +} else { + m2.ClearI32() +} +`, + }, + }, + + { + desc: "struct literal field", + in: ` +_ = &pb2.M2{ + I32: m2a.I32, +} +`, + want: map[Level]string{ + Red: ` +_ = pb2.M2_builder{ + I32: proto.ValueOrNil(m2a.HasI32(), m2a.GetI32), +}.Build() +`, + }, + }, + } + + runTableTests(t, tests) +}
diff --git a/internal/ignore/ignore.go b/internal/ignore/ignore.go new file mode 100644 index 0000000..3c02754 --- /dev/null +++ b/internal/ignore/ignore.go
@@ -0,0 +1,80 @@ +// 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 ignore implements checking if a certain file (typically .proto or +// .go) should be ignored by the open2opaque pipeline. +package ignore + +import ( + "os" + "path/filepath" + "strings" + + log "github.com/golang/glog" +) + +// List allows checking if a certain file (typically .proto or .go files) should +// be ignored by the open2opaque pipeline. +type List struct { + IgnoredFiles map[string]bool + IgnoredDirs []string +} + +// Add adds the depot path to the ignore list. +func (l *List) Add(path string) { + if strings.HasSuffix(path, "/") { + l.IgnoredDirs = append(l.IgnoredDirs, path) + return + } + l.IgnoredFiles[path] = true +} + +// Contains returns true if the loaded ignorelist contains path. +func (l *List) Contains(path string) bool { + if l == nil { + return false + } + for _, dir := range l.IgnoredDirs { + if strings.HasPrefix(path, dir) { + return true + } + } + return l.IgnoredFiles[path] +} + +// LoadList loads an ignore list from files matching the provided glob pattern +// (see http://godoc/3/file/base/go/file#Match for the syntax definition). +func LoadList(pattern string) (*List, error) { + matches, err := glob(pattern) + if err != nil { + return nil, err + } + var lines []string + for _, f := range matches { + b, err := os.ReadFile(f) + if err != nil { + return nil, err + } + lines = append(lines, strings.Split(strings.TrimSpace(string(b)), "\n")...) + } + l := &List{ + IgnoredFiles: make(map[string]bool, len(lines)), + } + for _, line := range lines { + if strings.HasPrefix(line, "#") { + continue // skip comments + } + line = strings.TrimSpace(line) + if line == "" { + continue // skip empty lines + } + l.Add(line) + } + log.Infof("Loaded ignore list from pattern %q: %d files and %d directories.", pattern, len(l.IgnoredFiles), len(l.IgnoredDirs)) + return l, nil +} + +var glob = func(pattern string) ([]string, error) { + return filepath.Glob(pattern) +}
diff --git a/internal/ignore/ignore_test.go b/internal/ignore/ignore_test.go new file mode 100644 index 0000000..4ed6a09 --- /dev/null +++ b/internal/ignore/ignore_test.go
@@ -0,0 +1,81 @@ +// 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 ignore_test + +import ( + "os" + "path/filepath" + "testing" + + "google.golang.org/open2opaque/internal/ignore" +) + +func TestIgnoreList(t *testing.T) { + dir := t.TempDir() + + if err := os.WriteFile(filepath.Join(dir, "file1.txt"), []byte(` +a/b/ + + `), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "file2.txt"), []byte(` +c/d/e/some.proto + `), 0644); err != nil { + t.Fatal(err) + } + + testCases := []struct { + loadPattern string + path string + want bool + }{ + { + loadPattern: filepath.Join(dir, "file*.txt"), + path: "a/b/some.proto", + want: true, + }, + { + loadPattern: filepath.Join(dir, "file1*.txt"), + path: "a/b/some.proto", + want: true, + }, + { + loadPattern: filepath.Join(dir, "file2*.txt"), + path: "a/b/some.proto", + want: false, + }, + { + loadPattern: filepath.Join(dir, "file*.txt"), + path: "a/b/x/some.proto", + want: true, + }, + { + loadPattern: filepath.Join(dir, "file*.txt"), + path: "a/x/some.proto", + want: false, + }, + { + loadPattern: filepath.Join(dir, "file*.txt"), + path: "c/d/e/some.proto", + want: true, + }, + { + loadPattern: filepath.Join(dir, "file1*.txt"), + path: "c/d/e/some.proto", + want: false, + }, + } + + for _, tc := range testCases { + ignoreList, err := ignore.LoadList(tc.loadPattern) + if err != nil { + t.Fatal(err) + } + if got := ignoreList.Contains(tc.path); got != tc.want { + t.Errorf("Using pattern %q, Contains(%s) = %v, want %v", tc.loadPattern, tc.path, got, tc.want) + } + } +}
diff --git a/internal/o2o/args/args.go b/internal/o2o/args/args.go new file mode 100644 index 0000000..81170f8 --- /dev/null +++ b/internal/o2o/args/args.go
@@ -0,0 +1,23 @@ +// 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 args translates command-line arguments from proto message names or +// prefixes to proto file names. +package args + +import ( + "context" + "strings" +) + +// ToProtoFilename translates the specified arg into a (filename, symbol) pair +// using go/global-protodb. If only a prefix is specified, symbol will be empty. +func ToProtoFilename(ctx context.Context, arg, kind string) (detectedKind, filename, symbol string, _ error) { + if kind == "proto_filename" || + (kind == "autodetect" && strings.HasSuffix(arg, ".proto")) { + return "proto_filename", arg, "", nil + } + + return "", arg, "", nil +}
diff --git a/internal/o2o/errutil/errutil.go b/internal/o2o/errutil/errutil.go new file mode 100644 index 0000000..5bee60a --- /dev/null +++ b/internal/o2o/errutil/errutil.go
@@ -0,0 +1,37 @@ +// 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 errutil provides utilities for easily annotating Go errors. +package errutil + +import "fmt" + +// Annotatef annotates non-nil error with the given message. +// +// It's designed to be used in a defer, for example: +// +// func g(arg string) (err error) { +// defer Annotate(&err, fmt.Sprintf("g(%s)") +// return errors.New("my error") +// } +// +// Calling g("hello") will result in error message: +// +// g(hello): my error +// +// Annotate allows using the above short form instead of the long form: +// +// func g(arg string) (err error) { +// defer func() { +// if err != nil { +// err = fmt.Errorf("g(%s): %v", arg, err) +// } +// }() +// return errors.New("my error") +// } +func Annotatef(err *error, format string, a ...any) { + if *err != nil { + *err = fmt.Errorf("%s: %v", fmt.Sprintf(format, a...), *err) + } +}
diff --git a/internal/o2o/errutil/errutil_test.go b/internal/o2o/errutil/errutil_test.go new file mode 100644 index 0000000..dab984e --- /dev/null +++ b/internal/o2o/errutil/errutil_test.go
@@ -0,0 +1,31 @@ +// 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 errutil + +import ( + "errors" + "testing" +) + +func TestAnnotateHasNoEffectOnNilError(t *testing.T) { + f := func() (err error) { + defer Annotatef(&err, "f") + return nil + } + if err := f(); err != nil { + t.Errorf("f() failed: %v", err) + } +} + +func TestAnnotateAddsInfoToNonNilError(t *testing.T) { + f := func() (err error) { + defer Annotatef(&err, "g") + return errors.New("test error") + } + want := "g: test error" + if err := f(); err == nil || err.Error() != want { + t.Errorf("f(): %v; want %s", err, want) + } +}
diff --git a/internal/o2o/fakeloader/fakeloader.go b/internal/o2o/fakeloader/fakeloader.go new file mode 100644 index 0000000..928c351 --- /dev/null +++ b/internal/o2o/fakeloader/fakeloader.go
@@ -0,0 +1,240 @@ +// 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 fakeloader contains a hermetic loader implementation that does not +// depend on any other systems and can be used in tests. +package fakeloader + +import ( + "bytes" + "context" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "strings" + "sync" + + "golang.org/x/tools/go/gcexportdata" + "google.golang.org/open2opaque/internal/o2o/loader" +) + +// ExportForFunc is a caller-supplied function that returns the export data (.x +// file) for the package with the specified import path. +type ExportForFunc func(importPath string) []byte + +type fakeLoader struct { + // fake input from the test + pkgs map[string][]string + files map[string]string + generated map[string]string + exportFor ExportForFunc +} + +// NewFakeLoader returns a Loader that can be used in tests. It works in the +// same way as loader returned from NewLoader, except it fakes expensive/flaky +// dependencies (e.g. executing commands). +// +// pkgs maps target rule names (e.g. "//base/go:flag") to list of files in that package +// (e.g. "net/proto2/go/open2opaque/testdata/dummycmd.go"). File paths should be in the form +// expected by blaze, e.g. "//net/proto2/go/open2opaque/testdata:dummy.go". Names of test packages +// should have "_test" suffix. +// +// files maps all files (e.g. "//test/pkg:updated.go"), referenced by the pkgs map, to their content. +// generated maps generated files (e.g. "//test/pkg:generated.go"), referenced by the pkgs map, to ther content. +// +// Unlike the loader returned by NewLoader, the fake loader is inexpensive. It's +// intended that multiple fake loaders are created (perhaps one per test). +func NewFakeLoader(pkgs map[string][]string, files, generated map[string]string, exportFor ExportForFunc) loader.Loader { + l := &fakeLoader{ + pkgs: pkgs, + files: files, + generated: generated, + exportFor: exportFor, + } + return l +} + +func (fl *fakeLoader) Close(context.Context) error { return nil } + +func (fl *fakeLoader) loadPackage(ctx context.Context, target *loader.Target) (_ *loader.Package, err error) { + // target.ID is e.g. google.golang.org/open2opaque/fix/testdata/dummy + files, ok := fl.pkgs[target.ID] + if !ok { + return nil, fmt.Errorf("no such fake package: %q", target.ID) + } + + pkgPath := target.ID + + pkg := &loader.Package{ + Fileset: token.NewFileSet(), + } + + var asts []*ast.File + for _, f := range files { + fname := f + generated := false + code, ok := fl.files[f] + if !ok { + if code, ok = fl.generated[f]; !ok { + return nil, errors.New("no source code") + } + generated = true + } + ast, err := parser.ParseFile(pkg.Fileset, fname, code, parser.ParseComments|parser.SpuriousErrors) + if err != nil { + return nil, err + } + asts = append(asts, ast) + pkg.Files = append(pkg.Files, &loader.File{ + AST: ast, + Path: fname, + Generated: generated, + Code: code, + }) + } + + pkg.TypeInfo = &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + } + imp, err := newFakeImporter(fl.files, fl.generated, fl.exportFor) + if err != nil { + return nil, err + } + cfg := types.Config{ + Importer: imp, + FakeImportC: true, + DisableUnusedImportCheck: true, + } + if pkg.TypePkg, err = cfg.Check(pkgPath, pkg.Fileset, asts, pkg.TypeInfo); err != nil { + // Ignore loading errors for the siloedpb package, emulating the + // behavior of the GAP loader with DropSiloedFiles enabled. + if !strings.Contains(err.Error(), "siloedpb") { + return nil, err + } + } + return pkg, nil +} + +// LoadPackages loads a batch of Go packages by concurrently calling +// loadPackage() for all targets, as the fake loader does not require any +// synchronization. +func (fl *fakeLoader) LoadPackages(ctx context.Context, targets []*loader.Target, res chan loader.LoadResult) { + var wg sync.WaitGroup + wg.Add(len(targets)) + for _, t := range targets { + t := t // copy + go func() { + defer wg.Done() + p, err := fl.loadPackage(ctx, t) + res <- loader.LoadResult{ + Target: t, + Package: p, + Err: err, + } + }() + } + wg.Wait() +} + +func newFakeImporter(files, generated map[string]string, exportFor ExportForFunc) (*fakeImporter, error) { + imp := &fakeImporter{ + checked: make(map[string]*types.Package), + files: files, + generated: generated, + exportFor: exportFor, + } + return imp, nil +} + +type fakeImporter struct { + checked map[string]*types.Package + files, generated map[string]string + exportFor ExportForFunc +} + +func (imp *fakeImporter) readXFile(importPath string, xFile []byte) (*types.Package, error) { + // Load the package from the .x file if we have not yet loaded it. + r, err := gcexportdata.NewReader(bytes.NewReader(xFile)) + if err != nil { + return nil, err + } + pkg, err := gcexportdata.Read(r, token.NewFileSet(), imp.checked, importPath) + if err != nil { + return nil, err + } + imp.checked[importPath] = pkg + return pkg, nil +} + +const fakeSync = ` +package sync + +type Once struct {} + +func (*Once) Do(func()) {} +` + +const fakeReflect = ` +package reflect + +type Type interface { + PkgPath() string +} + +func TypeOf(any) Type { return nil } +` + +const fakeContext = `package context + +type Context interface{} + +func Background() Context +` + +func (imp *fakeImporter) parseAndTypeCheck(pkgPath, fileName, contents string) (*types.Package, error) { + fs := token.NewFileSet() + afile, err := parser.ParseFile(fs, fileName, contents, parser.ParseComments|parser.SpuriousErrors) + if err != nil { + return nil, err + } + cfg := types.Config{Importer: imp} + pkg, err := cfg.Check(pkgPath, fs, []*ast.File{afile}, nil) + if err != nil { + return nil, err + } + imp.checked[pkgPath] = pkg + return pkg, nil +} + +func (imp *fakeImporter) Import(pkgPath string) (_ *types.Package, err error) { + if pkgPath == "unsafe" { + return types.Unsafe, nil + } + if p, ok := imp.checked[pkgPath]; ok && p.Complete() { + return p, nil + } + if pkgPath == "sync" { + return imp.parseAndTypeCheck(pkgPath, "sync.go", fakeSync) + } + if pkgPath == "reflect" { + return imp.parseAndTypeCheck(pkgPath, "reflect.go", fakeReflect) + } + if pkgPath == "context" { + return imp.parseAndTypeCheck(pkgPath, "context.go", fakeContext) + } + + b := imp.exportFor(pkgPath) + if b == nil { + return nil, fmt.Errorf("tried to load package %q for which there is no export data", pkgPath) + } + return imp.readXFile(pkgPath, b) +}
diff --git a/internal/o2o/loader/loader.go b/internal/o2o/loader/loader.go new file mode 100644 index 0000000..5e1de27 --- /dev/null +++ b/internal/o2o/loader/loader.go
@@ -0,0 +1,98 @@ +// 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 loader loads Go packages. +package loader + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" +) + +// Package represents a loaded Go package. +type Package struct { + Files []*File // Files in the package. + Fileset *token.FileSet // For translating between positions and locations in files. + TypeInfo *types.Info // Type information for the package (e.g. object identity, identifier uses/declarations, expression types). + TypePkg *types.Package // Describes the package (e.g. import objects, package scope). +} + +func (p Package) String() string { + var paths []string + for _, f := range p.Files { + paths = append(paths, f.Path) + } + return fmt.Sprintf("Go Package %s with files:\n\t%s", p.TypePkg.Path(), strings.Join(paths, "\n\t")) +} + +// File represents a single file in a loaded package. +type File struct { + // Parsed file. + AST *ast.File + + Path string + + // For go_test targets, this field indicates whether the file belongs to the + // test code itself (one or more _test.go files), or whether the file + // belongs to the package-under-test (via the go_test target’s library + // attribute). + // + // For other targets (go_binary or go_library), this field is always false. + LibraryUnderTest bool + + // Source code from the file. + Code string + + // True if the file was generated (go_embed_data, genrule, etc.). + Generated bool +} + +// Target represents a package to be loaded. It is identified by the opaque ID +// field, which is interpreted by the loader (which, in turn, delegates to the +// gopackagesdriver). +type Target struct { + // ID is an opaque identifier for the package when using the packages loader. + ID string + + // Testonly indicates that this package should be considered test code. This + // attribute needs to be passed in because go/packages does not have the + // concept of test only code, only Blaze does. + Testonly bool + + LibrarySrcs map[string]bool +} + +// LoadResult represents the result of loading an individual target. The Target +// field is always set. If something went wrong, Err is non-nil and Package is +// nil. Otherwise, Err is nil and Package is non-nil. +type LoadResult struct { + Target *Target + Package *Package + Err error +} + +// Loader loads Go packages. +type Loader interface { + // LoadPackages loads a batch of Go packages. + // + // The method does not return a slice of results, but instead writes each + // result to the specified result channel as the result arrives. This allows + // for concurrency with loaders that support it, like the Compilations + // Bigtable loader (processing results while the load is still ongoing). + LoadPackages(context.Context, []*Target, chan LoadResult) + Close(context.Context) error +} + +// LoadOne is a convenience function that loads precisely one target, saving the +// caller the mechanics of having to work with a batch of targets. +func LoadOne(ctx context.Context, l Loader, t *Target) (*Package, error) { + results := make(chan LoadResult, 1) + l.LoadPackages(context.Background(), []*Target{t}, results) + res := <-results + return res.Package, res.Err +}
diff --git a/internal/o2o/loader/loader_test.go b/internal/o2o/loader/loader_test.go new file mode 100644 index 0000000..187c292 --- /dev/null +++ b/internal/o2o/loader/loader_test.go
@@ -0,0 +1,204 @@ +// 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 loader_test + +import ( + "context" + "fmt" + "os" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/open2opaque/internal/o2o/fakeloader" + "google.golang.org/open2opaque/internal/o2o/loader" +) + +// fakingBlaze is true when NewFakeLoader is used +var fakingBlaze = true + +var prefix = func() string { + p := "google.golang.org/open2opaque/fake/" + return p +}() + +var pathPrefix = func() string { + p := "google.golang.org/open2opaque/fake/" + return p +}() + +var newLoader func(ctx context.Context, cfg *loader.Config) (loader.Loader, error) + +func init() { + testPkgs := map[string][]string{ + prefix + "dummy": []string{ + prefix + "dummy.go", + }, + prefix + "dummy_test": []string{ + prefix + "dummy.go", + prefix + "dummy_test.go", + }, + prefix + "dummycmd": []string{ + prefix + "dummycmd.go", + }, + } + testFiles := map[string]string{ + prefix + "dummy.go": `package dummy`, + prefix + "dummy_test.go": `package dummy`, + prefix + "dummycmd.go": `package main +func main() {}`, + } + newLoader = func(ctx context.Context, cfg *loader.Config) (loader.Loader, error) { + return fakeloader.NewFakeLoader(testPkgs, testFiles, nil, nil), nil + } +} + +var tests = []struct { + desc string + ruleName string + wantFiles []string +}{{ + desc: "library", + ruleName: prefix + "dummy", + wantFiles: []string{pathPrefix + "dummy.go"}, +}, { + desc: "test", + ruleName: prefix + "dummy_test", + wantFiles: []string{ + pathPrefix + "dummy.go", + pathPrefix + "dummy_test.go", + }, +}, { + desc: "binary", + ruleName: prefix + "dummycmd", + wantFiles: []string{pathPrefix + "dummycmd.go"}, +}} + +func setup(t *testing.T) (dir string) { + t.Helper() + dir, err := os.Getwd() + if err != nil { + t.Fatalf("os.Getwd failed: %v", err) + } + return dir +} + +// TestUsesRealBlaze exists so that the test output has a reminder that this +// test can be run locally with a real blaze. It also verifies that we don't +// accidentally fake blaze locally. +func TestUsesRealBlaze(t *testing.T) { + if fakingBlaze { + t.Skip("Can't use real blaze on borg. All tests will use a fake one. Run this test locally to use real blaze.") + } +} + +func testConfig(t *testing.T) *loader.Config { + t.Helper() + + cfg := &loader.Config{} + + return cfg +} + +func TestDiscoversSourceFiles(t *testing.T) { + setup(t) + l, err := newLoader(context.Background(), testConfig(t)) + if err != nil { + t.Fatal(err) + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName}) + if err != nil { + t.Fatalf("LoadPackage(%s) failed: %v", tt.ruleName, err) + } + var got []string + for _, f := range pkg.Files { + got = append(got, f.Path) + } + sort.Strings(got) + if d := cmp.Diff(tt.wantFiles, got); d != "" { + t.Errorf("LoadPackage(%s) = %v; want %v; diff:\n%s", tt.ruleName, got, tt.wantFiles, d) + } + }) + } +} + +func TestParallelQueries(t *testing.T) { + setup(t) + l, err := newLoader(context.Background(), testConfig(t)) + if err != nil { + t.Fatal(err) + } + errc := make(chan error) + const runs = 3 // chosen arbitrarily + for i := 0; i < runs; i++ { + go func() { + for _, tt := range tests { + pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName}) + if err != nil { + errc <- fmt.Errorf("%s: LoadPackage(%s) failed: %v", tt.desc, tt.ruleName, err) + continue + } + var got []string + for _, f := range pkg.Files { + got = append(got, f.Path) + } + sort.Strings(got) + if d := cmp.Diff(tt.wantFiles, got); d != "" { + errc <- fmt.Errorf("%s: LoadPackage(%s) = %v; want %v; diff:\n%s", tt.desc, tt.ruleName, got, tt.wantFiles, d) + } + errc <- nil + } + }() + } + for i := 0; i < runs*len(tests); i++ { + if err := <-errc; err != nil { + t.Error(err) + } + } +} + +func TestSingleFailureDoesntAffectOtherTargets(t *testing.T) { + setup(t) + l, err := newLoader(context.Background(), testConfig(t)) + if err != nil { + t.Fatal(err) + } + errc := make(chan error) + const runs = 5 // chosen arbitrarily + for i := 0; i < runs; i++ { + go func() { + for _, tt := range tests { + pkg, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: tt.ruleName}) + if err != nil { + errc <- fmt.Errorf("%s: LoadPackage(%s) failed: %v", tt.desc, tt.ruleName, err) + continue + } + var got []string + for _, f := range pkg.Files { + got = append(got, f.Path) + } + sort.Strings(got) + if d := cmp.Diff(tt.wantFiles, got); d != "" { + errc <- fmt.Errorf("%s: LoadPackage(%s) = %v; want %v; diff:\n%s", tt.desc, tt.ruleName, got, tt.wantFiles, d) + } + errc <- nil + } + in := "google.golang.org/open2opaque/internal/fix/testdata/DOES_NOT_EXIST" + got, err := loader.LoadOne(context.Background(), l, &loader.Target{ID: in}) + if err == nil { + errc <- fmt.Errorf("LoadPackage(%s) succeeded (files: %v); want failure (package doesn't exist)", in, got.Files) + return + } + errc <- nil + }() + } + for i := 0; i < runs*(len(tests)+1); i++ { + if err := <-errc; err != nil { + t.Error(err) + } + } +}
diff --git a/internal/o2o/loader/loaderblaze.go b/internal/o2o/loader/loaderblaze.go new file mode 100644 index 0000000..2d6e0e0 --- /dev/null +++ b/internal/o2o/loader/loaderblaze.go
@@ -0,0 +1,106 @@ +// 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 loader + +import ( + "context" + "fmt" + "os" + + "golang.org/x/tools/go/packages" +) + +type BlazeLoader struct { + dir string +} + +func NewBlazeLoader(ctx context.Context, cfg *Config, dir string) (*BlazeLoader, error) { + return &BlazeLoader{ + dir: dir, + }, nil +} + +// Close frees all resources that NewBlazeLoader() created. The BlazeLoader must +// not be used afterwards. +func (l *BlazeLoader) Close(context.Context) error { return nil } + +func failBatch(targets []*Target, res chan LoadResult, err error) { + for _, t := range targets { + res <- LoadResult{ + Target: t, + Err: err, + } + } +} + +// LoadPackage loads a batch of Go packages. +func (l *BlazeLoader) LoadPackages(ctx context.Context, targets []*Target, res chan LoadResult) { + targetByID := make(map[string]*Target) + patterns := make([]string, len(targets)) + for idx, t := range targets { + patterns[idx] = t.ID + targetByID[t.ID] = t + } + + cfg := &packages.Config{ + Dir: l.dir, + Context: ctx, + Mode: packages.NeedCompiledGoFiles | + packages.NeedSyntax | + packages.NeedTypes | + packages.NeedTypesInfo, + } + pkgs, err := packages.Load(cfg, patterns...) + if err != nil { + failBatch(targets, res, err) + return + } + + if got, want := len(pkgs), len(patterns); got != want { + failBatch(targets, res, fmt.Errorf("BUG: Load(%s) resulted in %d packages, want %d packages", patterns, got, want)) + return + } + + // Validate the response: ensure we can associate each returned package with + // a requested target, or fail the entire batch. + for _, pkg := range pkgs { + if _, ok := targetByID[pkg.ID]; !ok { + failBatch(targets, res, fmt.Errorf("BUG: Load() returned package %s, which was not requested", pkg.ID)) + return + } + } + +LoadedPackage: + for _, pkg := range pkgs { + t := targetByID[pkg.ID] + result := &Package{ + Fileset: pkg.Fset, + TypeInfo: pkg.TypesInfo, + TypePkg: pkg.Types, + } + for idx, absPath := range pkg.CompiledGoFiles { + b, err := os.ReadFile(absPath) + if err != nil { + res <- LoadResult{ + Target: t, + Err: err, + } + continue LoadedPackage + } + relPath := absPath + f := &File{ + AST: pkg.Syntax[idx], + Path: relPath, + LibraryUnderTest: t.LibrarySrcs[relPath], + Code: string(b), + } + result.Files = append(result.Files, f) + } + res <- LoadResult{ + Target: t, + Package: result, + } + } +}
diff --git a/internal/o2o/loader/loaderconfig.go b/internal/o2o/loader/loaderconfig.go new file mode 100644 index 0000000..2f1f886 --- /dev/null +++ b/internal/o2o/loader/loaderconfig.go
@@ -0,0 +1,9 @@ +// 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 loader + +// Config configures the loader. +type Config struct { +}
diff --git a/internal/o2o/profile/profile.go b/internal/o2o/profile/profile.go new file mode 100644 index 0000000..306c5b7 --- /dev/null +++ b/internal/o2o/profile/profile.go
@@ -0,0 +1,62 @@ +// 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 profile provides a simple way to gather timing statistics. +package profile + +import ( + "context" + "fmt" + "strings" + "time" +) + +type profile struct { + records []record +} + +type record struct { + name string + time time.Time +} + +type key int + +const profileKey key = 0 + +// NewContext returns a new context that has profiling information attached. +func NewContext(parent context.Context) context.Context { + ctx := context.WithValue(parent, profileKey, &profile{}) + Add(ctx, "start") + return ctx +} + +// Add adds event and event with the given name to the profile attached to the context. +func Add(ctx context.Context, name string) { + p, ok := ctx.Value(profileKey).(*profile) + if !ok { + return + } + p.records = append(p.records, record{ + name: name, + time: time.Now(), + }) +} + +// Dump prints the profile attached to the context as string. +func Dump(ctx context.Context) string { + p, ok := ctx.Value(profileKey).(*profile) + if !ok { + return "<no profile>" + } + if len(p.records) == 1 { + return "<empty profile>" + } + var b strings.Builder + fmt.Fprintf(&b, "TOTAL: %s | %s", p.records[len(p.records)-1].time.Sub(p.records[0].time), p.records[0].name) + for i := 1; i < len(p.records); i++ { + fmt.Fprintf(&b, " %s %s", p.records[i].time.Sub(p.records[i-1].time), p.records[i].name) + } + return b.String() +}
diff --git a/internal/o2o/rewrite/rewrite.go b/internal/o2o/rewrite/rewrite.go new file mode 100644 index 0000000..68163ea --- /dev/null +++ b/internal/o2o/rewrite/rewrite.go
@@ -0,0 +1,727 @@ +// 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 rewrite implements the main open2opaque functionality: rewriting Go +// source code to use the opaque API. +package rewrite + +import ( + "context" + "fmt" + "net/http" + "os" + "os/exec" + "regexp" + "sort" + "strings" + "sync" + "time" + + "flag" + log "github.com/golang/glog" + "github.com/google/subcommands" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" + "google.golang.org/open2opaque/internal/fix" + "google.golang.org/open2opaque/internal/ignore" + "google.golang.org/open2opaque/internal/o2o/errutil" + "google.golang.org/open2opaque/internal/o2o/loader" + "google.golang.org/open2opaque/internal/o2o/profile" + "google.golang.org/open2opaque/internal/o2o/syncset" + "google.golang.org/open2opaque/internal/o2o/wd" + "google.golang.org/protobuf/proto" + + statspb "google.golang.org/open2opaque/internal/dashboard" +) + +const kindTypeUsages = "typeusages" + +// Cmd implements the rewrite subcommand of the open2opaque tool. +type Cmd struct { + toUpdate string + toUpdateFile string + builderTypesFile string + builderLocationsFile string + levelsStr string + httpAddr string + outputFilterStr string + ignoreOutputFilterStr string + parallelJobs int + dryRun bool + showWork bool + useBuilders string +} + +func (cmd *Cmd) levels() []string { + return strings.Split(cmd.levelsStr, ",") +} + +// Name implements subcommand.Command. +func (*Cmd) Name() string { return "rewrite" } + +// Synopsis implements subcommand.Command. +func (*Cmd) Synopsis() string { return "Rewrite Go source code to use the Opaque API." } + +// Usage implements subcommand.Command. +func (*Cmd) Usage() string { + return `Usage: open2opaque rewrite -levels=yellow <target> [<target>...] + +See http://godoc/3/net/proto2/go/open2opaque/open2opaque for documentation. + +Command-line flag documentation follows: +` +} + +// SetFlags implements subcommand.Command. +func (cmd *Cmd) SetFlags(f *flag.FlagSet) { + const exampleMessageType = "google.golang.org/protobuf/types/known/timestamppb" + + f.StringVar(&cmd.toUpdate, + "types_to_update", + "", + "Comma separated list of types to migrate. For example, '"+exampleMessageType+"'. Empty means 'all'. types_to_update_file overrides this flag.") + + f.StringVar(&cmd.toUpdateFile, + "types_to_update_file", + "", + "Path to a file with one type to migrate per line. For example, '"+exampleMessageType+"'.") + + workdirHelp := "relative to the current directory" + + builderTypesFileDefault := "" + f.StringVar(&cmd.builderTypesFile, + "types_always_builders_file", + builderTypesFileDefault, + "Path to a file ("+workdirHelp+") with one type per line for which builders will always be used (instead of setters). For example, '"+exampleMessageType+"'.") + + builderLocationsFile := "" + builderLocationsPath := "path" + f.StringVar(&cmd.builderLocationsFile, + "paths_always_builders_file", + builderLocationsFile, + "Path to a file ("+workdirHelp+") with one "+builderLocationsPath+" per line for which builders will always be used (instead of setters).") + + levelsHelp := "" + f.StringVar(&cmd.levelsStr, + "levels", + "green", + "Comma separated list of rewrite levels"+levelsHelp+". Levels can be: green, yellow, red. Each level includes the preceding ones: -levels=red enables green, yellow and red rewrites. Empty list means that no rewrites are requested; only analysis.") + + f.StringVar(&cmd.httpAddr, + "http", + "localhost:6060", + "Address (host:port) to serve the net/http/pprof handlers on (for profiling).") + + f.StringVar(&cmd.outputFilterStr, + "output_filter", + ".", + "A regular expression that filters file names of files that should be written. This is useful, for example, to limit changes to '_test.go' files.") + + f.StringVar(&cmd.ignoreOutputFilterStr, + "ignore_output_filter", + "", + "A regular expression that filters out file names of files that should be written. This is useful, for example, to limit changes to non-test files. It takes precedence over --output_filter.") + + f.IntVar(&cmd.parallelJobs, + "parallel_jobs", + 20, + "How many packages are analyzed in parallel.") + + f.BoolVar(&cmd.dryRun, + "dry_run", + false, + "Do not modify any files, but run all the logic.") + + f.BoolVar(&cmd.showWork, + "show_work", + false, + "For debugging: show your work mode. Logs to the INFO log every rewrite step that causes a change, and the diff of its changes.") + + useBuildersDefault := "everywhere" + useBuildersHelp := "" + useBuildersValues := "'tests', 'everywhere' and 'nowhere'" + f.StringVar(&cmd.useBuilders, + "use_builders", + useBuildersDefault, + "Determines where struct initialization rewrites will use builders instead of setters. Valid values are "+useBuildersValues+"."+useBuildersHelp) +} + +// Execute implements subcommand.Command. +func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus { + if err := cmd.rewrite(ctx, f); err != nil { + // Use fmt.Fprintf instead of log.Exit to generate a shorter error + // message: users do not care about the current date/time and the fact + // that our code lives in rewrite.go. + fmt.Fprintf(os.Stderr, "%v\n", err) + return subcommands.ExitFailure + } + + return subcommands.ExitSuccess +} + +// Command returns an initialized Cmd for registration with the subcommands +// package. +func Command() *Cmd { + return &Cmd{} +} + +func (cmd *Cmd) rewrite(ctx context.Context, f *flag.FlagSet) error { + subdir, err := wd.Adjust() + if err != nil { + return err + } + + targets := f.Args() + _ = subdir + + if len(targets) == 0 { + f.Usage() + return nil + } + return cmd.RewriteTargets(ctx, targets) +} + +// RewriteTargets implements the rewrite functionality, and is called either +// from within this same package (open2opaque rewrite) or from the +// rewritepending package (open2opaque rewrite-pending wrapper). +func (cmd *Cmd) RewriteTargets(ctx context.Context, targets []string) error { + + targetsKind, err := verifyTargetsAreSameKind(targets) + if err != nil { + return err + } + if targetsKind == "unknown" { + return fmt.Errorf("could not detect target kind of %q - neither a blaze target, nor a .go file, nor a go package import path (see http://godoc/3/net/proto2/go/open2opaque/open2opaque for instructions)", targets[0]) + } + + inputTypeUses := targetsKind == kindTypeUsages + + if inputTypeUses && len(targets) > 1 { + return fmt.Errorf("When specifying the special value %q as target, you must not specify more than one target", kindTypeUsages) + } + + if inputTypeUses && cmd.toUpdate == "" && cmd.toUpdateFile == "" { + return fmt.Errorf("Please set either --types_to_update or --types_to_update_file to use %q", kindTypeUsages) + } + useSameClient := true + + outputFilterRe, err := regexp.Compile(cmd.outputFilterStr) + if err != nil { + return err + } + ignoreOutputFilterRe, err := regexp.Compile(cmd.ignoreOutputFilterStr) + if err != nil { + return err + } + + go func() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Useful commands") + fmt.Fprintln(w, " go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30") + fmt.Fprintln(w, " go tool pprof http://localhost:6060/debug/pprof/heap") + fmt.Fprintln(w, " wget http://localhost:6060/debug/pprof/trace?seconds=5") + fmt.Fprintln(w, " wget http://localhost:6060/debug/pprof/goroutine?debug=2") + }) + fmt.Println(http.ListenAndServe(cmd.httpAddr, nil)) + }() + + var lvls []fix.Level + for _, lvl := range cmd.levels() { + switch lvl { + case "": + case "green": + lvls = append(lvls, fix.Green) + case "yellow": + if useSameClient { + lvls = append(lvls, fix.Green) + } + lvls = append(lvls, fix.Yellow) + case "red": + if useSameClient { + lvls = append(lvls, fix.Green) + lvls = append(lvls, fix.Yellow) + } + lvls = append(lvls, fix.Red) + default: + return fmt.Errorf("unrecognized level name %q", lvl) + } + } + + toUpdateParts := strings.Split(cmd.toUpdate, ",") + if len(toUpdateParts) == 1 && toUpdateParts[0] == "" { + toUpdateParts = nil + } + typesToUpdate := newSet(toUpdateParts) + if cmd.toUpdateFile != "" { + b, err := os.ReadFile(cmd.toUpdateFile) + if err != nil { + log.ExitContext(ctx, err) + } + typesToUpdate = newSet(strings.Split(strings.TrimSpace(string(b)), "\n")) + } + + builderTypes := map[string]bool{} + if cmd.builderTypesFile != "" { + fn := cmd.builderTypesFile + b, err := os.ReadFile(fn) + if err != nil { + log.ExitContext(ctx, err) + } + builderTypes = newSet(strings.Split(strings.TrimSpace(string(b)), "\n")) + } + var builderLocations *ignore.List + if cmd.builderLocationsFile != "" { + fn := cmd.builderLocationsFile + l, err := ignore.LoadList(fn) + if err != nil { + if !os.IsNotExist(err) { + return err + } + // File not found: user is running a newer tool in a client synced + // to an older CL. Ignore and proceed without builderLocations. + } else { + builderLocations = l + } + } + + var pkgs []string + switch targetsKind { + + case "go package import path": + pkgs = targets + + default: + return fmt.Errorf("BUG: unhandled targetsKind %q", targetsKind) + } + + packagesToTargets := func(pkgs []string) ([]*loader.Target, error) { + fmt.Printf("Resolving Go package names...\n") + cfg := &packages.Config{ + Context: ctx, + } + loaded, err := packages.Load(cfg, pkgs...) + if err != nil { + return nil, err + } + targets := make([]*loader.Target, len(loaded)) + for idx, l := range loaded { + targets[idx] = &loader.Target{ID: l.ID} + } + return targets, nil + } + + targetsToRewrite, err := packagesToTargets(pkgs) + if err != nil { + return fmt.Errorf("can't read the package list: %v", err) + } + + var builderUseType fix.BuilderUseType + switch cmd.useBuilders { + case "everywhere": + builderUseType = fix.BuildersEverywhere + case "nowhere": + builderUseType = fix.BuildersNowhere + case "tests": + builderUseType = fix.BuildersTestsOnly + default: + return fmt.Errorf("invalid value for --use_builders flag. Valid values are 'tests', 'everywhere', 'everywhere-except-promising' and 'nowhere'") + } + + cfg := &config{ + targets: targetsToRewrite, + typesToUpdate: typesToUpdate, + builderTypes: builderTypes, + builderLocations: builderLocations, + levels: lvls, + outputFilterRe: outputFilterRe, + ignoreOutputFilterRe: ignoreOutputFilterRe, + useSameClient: useSameClient, + parallelJobs: cmd.parallelJobs, + dryRun: cmd.dryRun, + showWork: cmd.showWork, + useBuilder: builderUseType, + } + + if err := rewrite(ctx, cfg); err != nil { + log.ExitContext(ctx, err) + } + + return nil +} + +// splitName splits a qualified Go declaration name into package path +// and bare identifier. +func splitName(name string) (pkgPath, ident string) { + i := strings.LastIndex(name, ".") + return name[:i], name[i+1:] +} + +// keys returns the keys of set in sorted order. +func keys(set map[string]bool) []string { + var res []string + for k := range set { + res = append(res, k) + } + sort.Strings(res) + return res +} + +type config struct { + // pkgs is the list of Go packages to rewrite + targets []*loader.Target + + // Name of the run. This ends up (for example) in client names. + runName string + + // A set of types to consider when updating code + // (e.g. "google.golang.org/protobuf/types/known/timestamppb"). + // + // An empty (or nil) typesToUpdate means "update all types". + typesToUpdate map[string]bool + + // A set of types for which to always use builders, not setters. + // (e.g. "google.golang.org/protobuf/types/known/timestamppb"). + // + // An empty (or nil) builderTypes means: use setters or builders for + // production/test code respectively (or follow the -use_builders flag if + // set). + builderTypes map[string]bool + + builderLocations *ignore.List + + levels []fix.Level + + outputFilterRe, ignoreOutputFilterRe *regexp.Regexp + + useSameClient bool + + parallelJobs int + + dryRun bool + + showWork bool + + useBuilder fix.BuilderUseType +} + +func (c *config) createLoader(ctx context.Context, dir string) (_ loader.Loader, cl int64, _ error) { + + fmt.Println("Starting the Blaze loader") + l, err := loader.NewBlazeLoader(ctx, &loader.Config{}, dir) + if err != nil { + return nil, 0, err + } + return l, 0, nil +} + +func rewrite(ctx context.Context, cfg *config) (err error) { + defer errutil.Annotatef(&err, "rewrite() failed") + + log.InfoContextf(ctx, "Configuration: %+v", cfg) + + cutoff := "" + if len(cfg.targets) > 50 { + cutoff = " (listing first 50)" + } + fmt.Printf("rewriting %d packages:%s\n", len(cfg.targets), cutoff) + for idx, t := range cfg.targets { + fmt.Printf(" %s\n", t.ID) + if idx >= 50 { + break + } + } + + wd, err := os.Getwd() + if err != nil { + return err + } + + // Start the loader before creating more clients. This allows loading typeinfo sstables while we wait on citc to setup clients. + l, loaderCL, err := cfg.createLoader(ctx, wd) + if err != nil { + return err + } + defer l.Close(ctx) + + start := time.Now() + resc := make(chan fixResult) + + pkgCfg := packageConfig{ + loader: l, + outputFilterRe: cfg.outputFilterRe, + ignoreOutputFilterRe: cfg.ignoreOutputFilterRe, + dryRun: cfg.dryRun, + configuredPkg: fix.ConfiguredPackage{ + ProcessedFiles: syncset.New(), // avoid processing files multiple times + ShowWork: cfg.showWork, + TypesToUpdate: cfg.typesToUpdate, + Levels: cfg.levels, + UseBuilders: cfg.useBuilder, + }, + } + + // Load and process targets in batches of up to cfg.parallelJobs + // packages. This happens in a separate goroutine; the main goroutine just + // collects and prints results. + go func() { + ln := len(cfg.targets) + for idx := 0; idx < ln; idx += cfg.parallelJobs { + end := idx + cfg.parallelJobs + if end > ln { + end = ln + } + fixPackageBatch(ctx, pkgCfg, cfg.targets[idx:end], resc) + } + }() + + fmt.Printf("Loading packages (in batches of up to %d)...\n", cfg.parallelJobs) + + writtenByPath := make(map[string]bool) + var total, fail int + for range cfg.targets { + res := <-resc + profile.Add(res.ctx, "main/gotresp") + + total++ + if res.err != nil { + fail++ + } + + for p := range res.written { + writtenByPath[p] = true + } + + tused := time.Since(start) + tavg := tused / time.Duration(total) + tleft := time.Duration(len(cfg.targets)-total) * tavg + profile.Add(res.ctx, "done") + + fmt.Printf(`PROCESSED %d/%d packages + Last package: %s + Total time: %s + Package profile: %s + Failures: %d (%.2f%%) + Average time: %s + Estimated until done: %s + Estimated done at: %s + Error: %v + +`, total, len(cfg.targets), res.ruleName, tused, profile.Dump(res.ctx), fail, 100.0*float64(fail)/float64(total), tavg, tleft, time.Now().Add(tleft), res.err) + + } + + _ = loaderCL // Used in Google-internal code. + + writtenFiles := make([]string, 0, len(writtenByPath)) + for fname := range writtenByPath { + writtenFiles = append(writtenFiles, fname) + } + sort.Strings(writtenFiles) + + successful := total - fail + fmt.Printf("\nProcessed %d packages:\n", total) + fmt.Printf("\tsuccessfully analyzed: %d\n", successful) + fmt.Printf("\tfailed to load/rewrite: %d\n", fail) + fmt.Printf("\t.go files rewritten: %d\n", len(writtenFiles)) + if len(writtenFiles) > 0 { + fmt.Println("\nYou should see the modified files.") + if err := fixBuilds("", writtenFiles); err != nil { + fmt.Fprintf(os.Stderr, "Can't fix builds: %v\n", err) + } + } + if fail > 0 { + return fmt.Errorf("%d packages could not be rewritten", fail) + } + + return nil +} + +func runGoimports(dir string, files []string) error { + fmt.Printf("\tRunning goimports on %d files\n", len(files)) + // Limit concurrent processes. + parallelism := make(chan struct{}, 20) + eg, ctx := errgroup.WithContext(context.Background()) + for _, f := range files { + if !strings.HasSuffix(f, ".go") { + continue + } + eg.Go(func() error { + parallelism <- struct{}{} + defer func() { <-parallelism }() + cmd := exec.CommandContext(ctx, "goimports", "-w", f) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("goimports -w %s failed: %s\n%s", f, err, out) + } + return nil + }) + } + return eg.Wait() +} + +var fixBuilds = func(dir string, files []string) error { + return runGoimports(dir, files) +} + +type fixResult struct { + ruleName string + err error + stats []*statspb.Entry + ctx context.Context + drifted []string + written map[string]bool +} + +type packageConfig struct { + loader loader.Loader + outputFilterRe *regexp.Regexp + ignoreOutputFilterRe *regexp.Regexp + dryRun bool + configuredPkg fix.ConfiguredPackage +} + +func fixPackageBatch(ctx context.Context, cfg packageConfig, targets []*loader.Target, resc chan fixResult) { + results := make(chan loader.LoadResult, len(targets)) + var wg sync.WaitGroup + wg.Add(len(targets)) + for range targets { + go func() { + defer wg.Done() + res, ok := <-results + if !ok { + return // channel closed + } + ctx := profile.NewContext(ctx) + if err := res.Err; err != nil { + resc <- fixResult{ + ruleName: res.Target.ID, + err: err, + ctx: ctx, + } + return + } + profile.Add(ctx, "main/scheduled") + + cfg.configuredPkg.Testonly = res.Target.Testonly + cfg.configuredPkg.Loader = cfg.loader + cfg.configuredPkg.Pkg = res.Package + stats, drifted, written, err := fixPackage(ctx, cfg) + profile.Add(ctx, "main/fixed") + resc <- fixResult{ + ruleName: res.Target.ID, + err: err, + stats: stats, + ctx: ctx, + drifted: drifted, + written: written, + } + }() + } + cfg.loader.LoadPackages(ctx, targets, results) + close(results) + wg.Wait() +} + +// fixPackage loads a Go package +// from the input client, applies transformations to it, and writes results to +// the output client. +func fixPackage(ctx context.Context, cfg packageConfig) (stats []*statspb.Entry, drifted []string, written map[string]bool, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %s", r) + } + }() + + fixed, err := cfg.configuredPkg.Fix() + if err != nil { + return nil, nil, nil, err + } + profile.Add(ctx, "fix/fixed") + + written = make(map[string]bool) + for _, lvl := range cfg.configuredPkg.Levels { + for _, f := range fixed[lvl] { + fname := f.Path + if !f.Modified { + log.InfoContextf(ctx, "Skipping writing [NOT MODIFIED] %s %s to %s: not modified", lvl, f.Path, fname) + continue + } + if f.Generated { + log.InfoContextf(ctx, "Skipping writing [GENERATED FILE] %s %s to %s: generated files can't be overwritten", lvl, f.Path, fname) + continue + } + if strings.HasPrefix(f.Path, "net/proto2/go/") { + log.InfoContextf(ctx, "Skipping writing [IGNORED PATH] %s %s to %s: generated files can't be overwritten", lvl, f.Path, fname) + continue + } + if cfg.outputFilterRe.FindString(fname) == "" || cfg.ignoreOutputFilterRe.FindString(fname) != "" { + log.InfoContextf(ctx, "Skipping writing [OUTPUT FILTER] %s %s to %s", lvl, f.Path, fname) + continue + } + if cfg.dryRun { + log.InfoContextf(ctx, "Skipping writing [DRY RUN] %s %s to %s", lvl, f.Path, fname) + continue + } + if f.Drifted { + drifted = append(drifted, f.Path) + } + log.InfoContextf(ctx, "Writing %s %s to %s", lvl, f.Path, fname) + if err := os.WriteFile(fname, []byte(f.Code), 0644); err != nil { + return nil, nil, nil, err + } + written[fname] = true + } + } + profile.Add(ctx, "fix/wrotefiles") + + stats = fixed.AllStats() + profile.Add(ctx, "fix/donestats") + + return stats, drifted, written, nil +} + +func newSet(ss []string) map[string]bool { + if len(ss) == 0 { + return nil + } + out := make(map[string]bool) + for _, s := range ss { + out[s] = true + } + return out +} + +type rowAdder interface { + AddRow(context.Context, proto.Message) error +} + +type nullRowAdder struct{} + +func (*nullRowAdder) AddRow(context.Context, proto.Message) error { + return nil +} + +func targetKind(target string) string { + return "go package import path" +} + +func verifyTargetsAreSameKind(targets []string) (string, error) { + counters := make(map[string]int) + targetKinds := make([]string, len(targets)) + for idx, target := range targets { + kind := targetKind(target) + targetKinds[idx] = kind + counters[kind]++ + } + if len(counters) > 1 { + firstKind := targetKinds[0] + otherIdx := 0 + for otherIdx < len(targetKinds)-1 && targetKinds[otherIdx] == firstKind { + otherIdx++ + } + return "", fmt.Errorf("target kinds unexpectedly not the same: target %q is of kind %q, but target %q is of kind %q", targets[0], firstKind, targets[otherIdx], targetKinds[otherIdx]) + } + return targetKinds[0], nil +}
diff --git a/internal/o2o/setapi/setapi.go b/internal/o2o/setapi/setapi.go new file mode 100644 index 0000000..1143a8b --- /dev/null +++ b/internal/o2o/setapi/setapi.go
@@ -0,0 +1,816 @@ +// 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 setapi implements the setapi open2opaque subcommand, which sets the +// go_api_flag file option in .proto files. +package setapi + +import ( + "bytes" + "cmp" + "context" + "errors" + "fmt" + "io" + "math" + "os" + "os/exec" + "slices" + "strings" + + "flag" + "github.com/google/subcommands" + "golang.org/x/sync/errgroup" + pb "google.golang.org/open2opaque/internal/apiflagdata" + "google.golang.org/open2opaque/internal/o2o/args" + "google.golang.org/open2opaque/internal/protodetect" + "google.golang.org/open2opaque/internal/protoparse" + descpb "google.golang.org/protobuf/types/descriptorpb" + gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" +) + +// Cmd implements the setapi subcommand of the open2opaque tool. +type Cmd struct { + apiFlag string + inputFile string + skipCleanup bool + maxProcs uint + protoFmt string + kind string +} + +// Name implements subcommand.Command. +func (*Cmd) Name() string { return "setapi" } + +// Synopsis implements subcommand.Command. +func (*Cmd) Synopsis() string { + return "Set the Go API level proto option." +} + +// Usage implements subcommand.Command. +func (*Cmd) Usage() string { + return `Usage: open2opaque setapi [-api=<` + strings.Join(validApis, "|") + `>] [-input_file=<input-list>] [<path/file.proto>] [<protopkg1>] [<protopkg2.MessageName>] + +The setapi subcommand adjusts the Go API level option on the specified proto file(s) / package(s) / message(s). + +The setapi subcommand can either read the proto file name(s) / package(s) / message(s) +from a text file (-input_file) or from the command line arguments, or both. + +Command-line flag documentation follows: +` +} + +// SetFlags implements subcommand.Command. +func (cmd *Cmd) SetFlags(f *flag.FlagSet) { + f.StringVar(&cmd.apiFlag, "api", "OPAQUE", "set Go API level value to this, valid values: OPEN, HYBRID, and OPAQUE") + f.StringVar(&cmd.inputFile, "input_file", "", "file containing list of proto source files / proto packages / proto messages to update (one per line)") + f.BoolVar(&cmd.skipCleanup, "skip_cleanup", false, "skip the cleanup step, which removes the file-level flag if it equals the default and removes message flags if they equal the file-level API") + f.UintVar(&cmd.maxProcs, "max_procs", 32, "max number of files concurrently processed") + protofmtDefault := "" + f.StringVar(&cmd.protoFmt, "protofmt", protofmtDefault, "if non-empty, a formatter program for .proto files") +} + +// Execute implements subcommand.Command. +func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus { + if err := cmd.setapi(ctx, f); err != nil { + // Use fmt.Fprintf instead of log.Exit to generate a shorter error + // message: users do not care about the current date/time and the fact + // that our code lives in setapi.go. + fmt.Fprintf(os.Stderr, "%v\n", err) + return subcommands.ExitFailure + } + return subcommands.ExitSuccess +} + +// Command returns an initialized Cmd for registration with the subcommands +// package. +func Command() *Cmd { + return &Cmd{} +} + +// Task defines the modification of the Go API level of a proto file or a +// particular message. +type Task struct { + // Path of the proto file. + Path string + // The content of the proto file. + Content []byte + // If not empty, set the API level only for the message with this + // package-local or fully-qualified name, e.g. Msg.NestedMsg or + // pkgname.Msg.NestedMsg. + // If empty, set the file-level API level. + Symbol string + + TargetAPI gofeaturespb.GoFeatures_APILevel + // If true, skip cleanup steps. If false, perform the following steps: + // - if all messages in a file are on the same API level, set the whole file + // to that level. + // - remove API flags from all messages that don't need it because they are + // on the same API level as the file (or of its parent message for nested + // messages in edition protos that use the new edition-feature API flag). + SkipCleanup bool + // A leading comment before the Go API flag prevents setapi from + // modifying it. If a modification was prevent by this mechanism and + // ErrorOnExempt is true, Process returns an error. Otherwise, the original + // content is returned. + ErrorOnExempt bool +} + +func (cmd *Cmd) setapi(ctx context.Context, f *flag.FlagSet) error { + api, err := parseAPIFlag(cmd.apiFlag) + if err != nil { + return err + } + + var inputs []string + if cmd.inputFile != "" { + var err error + if inputs, err = readInputFile(cmd.inputFile); err != nil { + return fmt.Errorf("error reading file: %v", err) + } + } + inputs = append(inputs, f.Args()...) + + var tasks []Task + kind := cmd.kind + if kind == "" { + kind = "proto_filename" + } + for _, input := range inputs { + _, filename, symbol, err := args.ToProtoFilename(ctx, input, kind) + if err != nil { + return err + } + content, err := os.ReadFile(filename) + if err != nil { + return err + } + tasks = append(tasks, Task{ + Path: filename, + Symbol: symbol, + Content: content, + TargetAPI: api, + SkipCleanup: cmd.skipCleanup, + ErrorOnExempt: true, + }) + } + + if len(tasks) == 0 { + return fmt.Errorf("missing inputs, use either -list (one input per line) and / or pass input file name(s) / package(s) / message(s) as non-flag arguments") + } + + // Error out if there are non-unique paths to avoid conflicting inputs like + // editing the file-level flag of a file and a message flag in the same file. + if conflicts := conflictingTasks(tasks); len(conflicts) > 0 { + var l []string + for _, c := range conflicts { + l = append(l, c.Path) + } + return fmt.Errorf("conflicting, non-unique proto files in inputs: %s", strings.Join(l, ", ")) + } + + outputs := make([][]byte, len(tasks)) + + protofmt := cmd.protoFmt + if protofmt == "" { + protofmt = "cat" + } + eg, ctx := errgroup.WithContext(ctx) + eg.SetLimit(int(cmd.maxProcs)) + for itask, task := range tasks { + itask, task := itask, task + eg.Go(func() error { + var err error + outputs[itask], err = Process(ctx, task, protofmt) + return err + }) + } + if err := eg.Wait(); err != nil { + return fmt.Errorf("aborting without writing any proto files: %v", err) + } + + // Write the outputs back to the input files. + eg, ctx = errgroup.WithContext(ctx) + eg.SetLimit(int(cmd.maxProcs)) + for itask, task := range tasks { + itask, task := itask, task + eg.Go(func() error { + return os.WriteFile(task.Path, outputs[itask], 0644) + }) + } + if err := eg.Wait(); err != nil { + return fmt.Errorf("error while writing proto files: %v", err) + } + return nil +} + +var ( + apiMap = map[string]gofeaturespb.GoFeatures_APILevel{ + "OPEN": gofeaturespb.GoFeatures_API_OPEN, + "HYBRID": gofeaturespb.GoFeatures_API_HYBRID, + "OPAQUE": gofeaturespb.GoFeatures_API_OPAQUE, + } + // Don't use the keys of apiMap to report valid values to the user, we want + // in a specific order. + validApis = []string{"OPEN", "HYBRID", "OPAQUE"} +) + +func parseAPIFlag(flag string) (gofeaturespb.GoFeatures_APILevel, error) { + if flag == "" { + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("missing --api flag value") + } + if api, ok := apiMap[flag]; ok { + return api, nil + } + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("invalid --api flag value: %v, valid values: %s", flag, strings.Join(validApis, ", ")) +} + +func readInputFile(name string) ([]string, error) { + b, err := os.ReadFile(name) + if err != nil { + return nil, err + } + var result []string + for _, line := range strings.Split(string(b), "\n") { + if line := strings.TrimSpace(line); line != "" { + result = append(result, line) + } + } + return result, nil +} + +func conflictingTasks(tasks []Task) []Task { + var conflicts []Task + present := make(map[string]bool) + for _, task := range tasks { + if present[task.Path] { + conflicts = append(conflicts, task) + } + present[task.Path] = true + } + return conflicts +} + +var logf = func(format string, a ...any) { fmt.Fprintf(os.Stderr, "[setapi] "+format+"\n", a...) } + +func parse(path string, content []byte, skipMessages bool) (*protoparse.FileOpt, error) { + parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) { + return io.NopCloser(bytes.NewReader(content)), nil + }) + fopt, err := parser.ParseFile(path, skipMessages) + if err != nil { + return nil, fmt.Errorf("protoparse.ParseFile: %v", err) + } + return fopt, nil +} + +func parentAPI(path string, content []byte, msgName string) (gofeaturespb.GoFeatures_APILevel, error) { + fopt, err := parse(path, content, false) + if err != nil { + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, err + } + msgName = strings.TrimPrefix(msgName, fopt.Package+".") + var mopt *protoparse.MessageOpt + for _, moptLoop := range fopt.MessageOpts { + if o := findMsg(moptLoop, msgName); o != nil { + mopt = o + break + } + } + if mopt == nil { + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("cannot find message %q", msgName) + } + result := fopt.GoAPI + if fopt.Syntax == "editions" && mopt.Parent != nil { + result = mopt.Parent.GoAPI + } + return result, nil +} + +func traverseMsgTree(opt *protoparse.MessageOpt, f func(*protoparse.MessageOpt) error) error { + if err := f(opt); err != nil { + return err + } + for _, c := range opt.Children { + if err := traverseMsgTree(c, f); err != nil { + return err + } + } + return nil +} + +// Process modifies the API level of a proto file or of a particular message in +// a proto file, see the doc comment of the type Task for more details. Before +// returning the modified file content, the file is formatted by executing +// formatter (use "cat" if you don't have a formatter handy). This function +// doesn't modify the []byte task.Content. +func Process(ctx context.Context, task Task, formatter string) ([]byte, error) { + if task.Path == "" { + return nil, fmt.Errorf("path is empty") + } + if len(task.Content) == 0 { + return nil, fmt.Errorf("content is empty") + } + + // Clone the input file content in case the caller will use the slice after + // the call. + content := slices.Clone(task.Content) + + var err error + if task.Symbol == "" { + content, err = setFileAPI(task.Path, content, task.TargetAPI, task.SkipCleanup, task.ErrorOnExempt) + if err != nil { + return nil, fmt.Errorf("setFileAPI: %v", err) + } + } else { + parentAPI, err := parentAPI(task.Path, content, task.Symbol) + if err != nil { + return nil, fmt.Errorf("parentAPI: %v", err) + } + content, err = setMsgAPI(task.Path, content, task.Symbol, parentAPI, task.TargetAPI, task.SkipCleanup) + if err != nil { + if errors.Is(err, ErrLeadingCommentPreventsEdit) && !task.ErrorOnExempt { + // Don't error out if leading comment exempted edit but ErrorOnExempt + // is false. Could write this a lot more compact, but this makes it easy + // to understand. + } else { + return nil, err + } + } + } + + if !task.SkipCleanup { + content, err = cleanup(task.Path, content) + if err != nil { + return nil, fmt.Errorf("cleanup: %v", err) + } + } + content, err = FormatFile(ctx, content, formatter) + if err != nil { + return nil, fmt.Errorf("FormatFile: %v", err) + } + return content, nil +} + +func fromFeatureToOld(apiLevel gofeaturespb.GoFeatures_APILevel) pb.GoAPI { + switch apiLevel { + case gofeaturespb.GoFeatures_API_OPEN: + return pb.GoAPI_OPEN_V1 + + case gofeaturespb.GoFeatures_API_HYBRID: + return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID + + case gofeaturespb.GoFeatures_API_OPAQUE: + return pb.GoAPI_OPAQUE_V0 + + default: + panic(fmt.Sprintf("unknown apilevel %v", apiLevel)) + } +} + +func byteRangeWithEOLComment(in []byte, tr protoparse.TextRange) (beginByte, endByte int, err error) { + beginByte, endByte, err = tr.ToByteRange(in) + if err != nil { + return -1, -1, fmt.Errorf("tr.ToByteRange: %v", err) + } + if tr.BeginLine == tr.EndLine { + if nextNewlineByte := bytes.IndexByte(in[endByte:], '\n'); nextNewlineByte != -1 { + if lineSuffix := in[endByte : endByte+nextNewlineByte]; bytes.HasPrefix(bytes.TrimSpace(lineSuffix), []byte("//")) { + endByte += nextNewlineByte + } + } + } + return beginByte, endByte, nil +} + +func replaceTextRange(in []byte, tr protoparse.TextRange, insert []byte) ([]byte, error) { + beginByte, endByte, err := tr.ToByteRange(in) + if err != nil { + return nil, fmt.Errorf("tr.ToByteRange: %v", err) + } + in = slices.Delete(in, beginByte, endByte) + return slices.Insert(in, beginByte, insert...), nil +} + +func setFileAPI(path string, content []byte, targetAPI gofeaturespb.GoFeatures_APILevel, skipCleanup, errorOnExempt bool) ([]byte, error) { + fopt, err := parse(path, content, true) + if err != nil { + return nil, err + } + if fopt.IsExplicit && fopt.APIInfo == nil { + return nil, fmt.Errorf("BUG: fopt.APIInfo is nil") + } + + if defaultAPI := protodetect.DefaultFileLevel(path); defaultAPI == targetAPI { + if !fopt.IsExplicit { + logf("File %s is already on the target API level by default, doing nothing", path) + return content, nil + } + if fopt.APIInfo.HasLeadingComment { + if errorOnExempt { + return nil, fmt.Errorf("API flag of file %s has a leading comment that prevents removing it", path) + } + logf("API flag of file %s has a leading comment that prevents removing it", path) + return content, nil + } + if fopt.GoAPI == targetAPI && skipCleanup { + logf("skipping cleanup: not removing the API flag of file %s although it's at the default API level", path) + return content, nil + } + from, to, err := byteRangeWithEOLComment(content, fopt.APIInfo.TextRange) + if err != nil { + return nil, fmt.Errorf("byteRangeWithEOLComment: %v", err) + } + logf("Removing the API flag of file %s to use the default API level", path) + return slices.Delete(content, from, to), nil + } + + if !fopt.IsExplicit { + logf("Inserting API flag %q into file %s", targetAPI, path) + + ln, err := fileOptionLineNumber(fopt.SourceCodeInfo) + if err != nil { + return nil, fmt.Errorf("fileOptionLineNumber: %v", err) + } + lines := bytes.Split(content, []byte{'\n'}) + insertLine := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI)) + if fopt.Syntax == "editions" { + insertLine = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI) + } + result := slices.Clone(lines[:ln]) + result = append(result, []byte(insertLine)) + result = append(result, lines[ln:]...) + return bytes.Join(result, []byte{'\n'}), nil + } + + // File is currently explicitly set to API value and target API isn't the + // default API. + if fopt.GoAPI == targetAPI { + logf("File %s is already on the target API level, do nothing", path) + return content, nil + } + if fopt.APIInfo.HasLeadingComment { + if errorOnExempt { + return nil, fmt.Errorf("API flag of file %s has a leading comment that prevents replacing it", path) + } + logf("API flag of file %s has a leading comment that prevents replacing it", path) + return content, nil + } + logf("Replacing the API flag of file %s", path) + insert := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI)) + if fopt.Syntax == "editions" { + insert = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI) + } + content, err = replaceTextRange(content, fopt.APIInfo.TextRange, []byte(insert)) + if err != nil { + return nil, fmt.Errorf("replaceTextRange: %v", err) + } + return content, nil +} + +func findMsg(opt *protoparse.MessageOpt, name string) *protoparse.MessageOpt { + if opt.Message == name { + return opt + } + for _, c := range opt.Children { + if childOpt := findMsg(c, name); childOpt != nil { + return childOpt + } + } + return nil +} + +// ErrLeadingCommentPreventsEdit signals that no modifications were made because +// a leading comment exempted an API flag from modification. This error is +// ignored if Task.ErrorOnExempt is false. +var ErrLeadingCommentPreventsEdit = errors.New("leading comment prevents edit, check the logs for more details") + +func setMsgAPI(path string, content []byte, msgName string, parentAPI, targetAPI gofeaturespb.GoFeatures_APILevel, skipCleanup bool) ([]byte, error) { + // This function is called recursively. Parse content every time because the + // position information changes when parent messages are manipulated. + fopt, err := parse(path, content, false) + if err != nil { + return nil, err + } + msgName = strings.TrimPrefix(msgName, fopt.Package+".") + var mopt *protoparse.MessageOpt + for _, moptLoop := range fopt.MessageOpts { + if o := findMsg(moptLoop, msgName); o != nil { + mopt = o + break + } + } + if mopt == nil { + return nil, fmt.Errorf("cannot find massage %q", msgName) + } + if mopt.IsExplicit && mopt.APIInfo == nil { + return nil, fmt.Errorf("BUG: mopt.APIInfo is nil") + } + + if parentAPI == targetAPI { + if !mopt.IsExplicit { + logf("Message %q is already on the target API level, doing nothing", msgName) + return content, nil + } + if mopt.APIInfo.HasLeadingComment { + logf("Changing API level of message %q was prevented by a leading comment of the API flag", msgName) + if mopt.GoAPI != targetAPI { + // Report an error to abort the whole operation if we would have + // changed the API flag instead of just cleaning it up because it's the + // same as the parent. If we didn't, we might change nested messages to + // the wrong API level. + return nil, ErrLeadingCommentPreventsEdit + } + return content, nil + } + if mopt.GoAPI != targetAPI && fopt.Syntax == "editions" { + logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName) + for _, child := range mopt.Children { + content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup) + if err != nil { + return nil, err + } + } + } + if mopt.GoAPI == targetAPI && skipCleanup { + logf("skipping cleanup: not removing the API flag of message %q although it's at the parent API level", msgName) + return content, nil + } + from, to, err := byteRangeWithEOLComment(content, mopt.APIInfo.TextRange) + if err != nil { + return nil, fmt.Errorf("byteRangeWithEOLComment: %v", err) + } + logf("Removing the API flag of message %q to use the parent API level", msgName) + return slices.Delete(content, from, to), nil + } + + if !mopt.IsExplicit { + if fopt.Syntax == "editions" { + logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName) + for _, child := range mopt.Children { + content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup) + if err != nil { + return nil, err + } + } + } + logf("Inserting API flag %q into message %q", targetAPI, msgName) + idx, err := msgOptionInsertionByteIdx(content, fopt.SourceCodeInfo, mopt.LocPath) + if err != nil { + return nil, fmt.Errorf("msgOptionInsertionByteIdx: %v", err) + } + insertion := fmt.Sprintf("\noption go_api_flag = %q;", fromFeatureToOld(targetAPI)) + if fopt.Syntax == "editions" { + insertion = fmt.Sprintf("\noption features.(pb.go).api_level = %s;", targetAPI) + } + return slices.Concat(content[:idx], []byte(insertion), content[idx:]), nil + } + + // Message is currently explicitly set to API value and target API isn't the + // parent API. + if mopt.GoAPI == targetAPI { + logf("Message %q is already on the target API level, do nothing", msgName) + return content, nil + } + if mopt.APIInfo.HasLeadingComment { + logf("Changing API level of message %q was prevented by a leading comment of the API flag", msgName) + return content, ErrLeadingCommentPreventsEdit + } + if fopt.Syntax == "editions" { + logf("Before changing API flag of message %q, descending into children to prevent recursive change of API level", msgName) + for _, child := range mopt.Children { + content, err = setMsgAPI(path, content, child.Message, targetAPI, child.GoAPI, skipCleanup) + if err != nil { + return nil, err + } + } + } + logf("Replacing the API flag of message %q", msgName) + insert := fmt.Sprintf("option go_api_flag = %q;", fromFeatureToOld(targetAPI)) + if fopt.Syntax == "editions" { + insert = fmt.Sprintf("option features.(pb.go).api_level = %s;", targetAPI) + } + content, err = replaceTextRange(content, mopt.APIInfo.TextRange, []byte(insert)) + if err != nil { + return nil, fmt.Errorf("replaceTextRange: %v", err) + } + return content, nil +} + +func cleanup(path string, content []byte) ([]byte, error) { + // Cleanup 1: If all messages are on the same API level, flip the file API + // level to that value. If said level is the default for the path, don't use + // an explicit flag. + fopt, err := parse(path, content, false) + if err != nil { + return nil, err + } + apiMap := map[gofeaturespb.GoFeatures_APILevel]struct{}{} + for _, moptLoop := range fopt.MessageOpts { + traverseMsgTree(moptLoop, func(mopt *protoparse.MessageOpt) error { + apiMap[mopt.GoAPI] = struct{}{} + return nil + }) + } + if len(apiMap) == 1 { + // All messages have same API level. + // Consider cleanup as a nice to have, don't error out on leading-comment + // exemptions. + const errorOnExempt = false + const skipCleanup = false + content, err = setFileAPI(path, content, fopt.MessageOpts[0].GoAPI, skipCleanup, errorOnExempt) + if err != nil { + return nil, fmt.Errorf("setFileAPI: %v", err) + } + } + + // Cleanup 2: Remove the API flag from all messages that have the same API + // level as their parents. The parent is usually the file API level, but in + // case of editions proto that use the new edition feature, the parent of + // a nested message is its parent message. + // + // Parse again after the previous cleanup might have modified conten. + fopt, err = parse(path, content, false) + if err != nil { + return nil, err + } + // Collect all byte ranges of API flags that should be removed. + type byteRange struct { + from, to int + } + var removeByteRanges []byteRange + for _, moptLoop := range fopt.MessageOpts { + if err := traverseMsgTree(moptLoop, func(mopt *protoparse.MessageOpt) error { + if !mopt.IsExplicit { + return nil + } + if mopt.APIInfo == nil { + return fmt.Errorf("BUG: mopt.APIInfo is nil") + } + if mopt.APIInfo.HasLeadingComment { + return nil + } + parentAPI := fopt.GoAPI + if fopt.Syntax == "editions" && mopt.Parent != nil { + parentAPI = mopt.Parent.GoAPI + } + if mopt.GoAPI != parentAPI { + return nil + } + // Message has same API level as parent (or file) and API flag is + // explicit. + from, to, err := byteRangeWithEOLComment(content, mopt.APIInfo.TextRange) + if err != nil { + return fmt.Errorf("TextRange.ToByteRange: %v", err) + } + removeByteRanges = append(removeByteRanges, byteRange{from: from, to: to}) + return nil + }); err != nil { + return nil, err + } + } + if len(removeByteRanges) == 0 { + return content, nil + } + + slices.SortFunc(removeByteRanges, func(a, b byteRange) int { + return cmp.Compare(a.from, b.from) + }) + for i := 1; i < len(removeByteRanges); i++ { + if removeByteRanges[i].from < removeByteRanges[i-1].to { + return nil, fmt.Errorf("text ranges overlap") + } + } + + for _, br := range slices.Backward(removeByteRanges) { + content = slices.Delete(content, br.from, br.to) + } + return content, nil +} + +func spanEndLine(span []int32) int32 { + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1209 + if len(span) == 4 { + return span[2] + } + return span[0] +} + +// fileOptionLineNumber returns the line number to insert the file-level +// go_api_flag option. It uses the following heuristics to determine the +// line number: +// If there are any file option settings, return the line number after the last +// one. +// If there are any import lines, return the line number after the last one. +// If there is a package statement line, return the line number after it. +// If there is a syntax statement line, return the line number after it. +// Note that this func assumes that an earlier protoparser.Parser.ParseFile +// invocation will return error already if there is no syntax statement. +func fileOptionLineNumber(info *descpb.SourceCodeInfo) (int32, error) { + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L105-L137 + syntaxFieldNum = 12 + editionFieldNum = 14 + editionDeprFieldNum = 13 + packageFieldNum = 2 + importFieldNum = 3 + optionFieldNum = 8 + ) + // pos contains a mapping of proto field numbers to line numbers. For syntax + // and package constructs, the line numbers represent where they are declared. + // For import and options, the line number represents the last import/option + // if it exists. + pos := map[int32]int32{} + for _, loc := range info.GetLocation() { + path := loc.GetPath() + span := loc.GetSpan() + if len(path) < 1 { + continue + } + switch fieldNum := path[0]; fieldNum { + case syntaxFieldNum, editionFieldNum, editionDeprFieldNum, packageFieldNum, importFieldNum, optionFieldNum: + endLine := spanEndLine(span) + // If not set, map returns 0. + if endLine > pos[fieldNum] { + pos[fieldNum] = endLine + } + } + } + + if lnum, ok := pos[optionFieldNum]; ok { + return lnum + 1, nil + } + if lnum, ok := pos[importFieldNum]; ok { + return lnum + 1, nil + } + if lnum, ok := pos[packageFieldNum]; ok { + return lnum + 1, nil + } + if lnum, ok := pos[syntaxFieldNum]; ok { + return lnum + 1, nil + } + if lnum, ok := pos[editionFieldNum]; ok { + return lnum + 1, nil + } + if lnum, ok := pos[editionDeprFieldNum]; ok { + return lnum + 1, nil + } + + return 0, fmt.Errorf("cannot determine line number for file-level API flag") +} + +func msgOptionInsertionByteIdx(content []byte, info *descpb.SourceCodeInfo, msgPath []int32) (int, error) { + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L105 + nameFieldNum = 1 + ) + namePath := append(slices.Clone(msgPath), nameFieldNum) + nameEndIdx := math.MaxInt + + for _, loc := range info.GetLocation() { + path := loc.GetPath() + if len(path) <= len(msgPath) { + // Paths in the message (name, fields, option, ...) have to be longer than + // the message path. + continue + } + if slices.Equal(path, namePath) { + var err error + _, nameEndIdx, err = protoparse.SpanToTextRange(loc.GetSpan()).ToByteRange(content) + if err != nil { + return -1, fmt.Errorf("TextRange.ToByteRange: %v", err) + } + break + } + } + if nameEndIdx == math.MaxInt { + return -1, fmt.Errorf("BUG: cannot find message name") + } + + // Look for the next opening curly after the name. If this happens to be in a + // comment, this function will produce wrong results. + offset := bytes.IndexByte(content[nameEndIdx:], '{') + if offset == -1 { + return -1, fmt.Errorf(`cannot find "{"`) + } + return nameEndIdx + offset + 1, nil +} + +// FormatFile runs formatter on input and returns the formatted result. +func FormatFile(ctx context.Context, input []byte, formatter string) ([]byte, error) { + cmd := exec.CommandContext(ctx, formatter) + cmd.Stdin = bytes.NewReader(input) + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cmd.Stdout = stdout + cmd.Stderr = stderr + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("formatter error: %v", err) + } + if stderr.Len() > 0 { + return nil, fmt.Errorf("formatter stderr: %s", stderr.String()) + } + return stdout.Bytes(), nil +}
diff --git a/internal/o2o/setapi/setapi_test.go b/internal/o2o/setapi/setapi_test.go new file mode 100644 index 0000000..b70ad49 --- /dev/null +++ b/internal/o2o/setapi/setapi_test.go
@@ -0,0 +1,1245 @@ +// 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 setapi_test + +import ( + "context" + "errors" + "os" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/open2opaque/internal/o2o/setapi" + gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" +) + +// We added new test functions for file modification below that cover the same +// and more aspects of setapi. But we keep this test function around since it +// still works and there is no point in taking the risk of reducing test +// coverage. However, if this should become a maintenance burden in the future, +// consider removing this function. +func TestModificationOld(t *testing.T) { + const copyrightHeader = `// 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. + +` + testcases := []struct { + desc string + input string + task setapi.Task + want string + }{ + { + input: "../../../testdata/flag_edition_test1_go_proto/flag_edition_test1.proto", + desc: "replace_file_option_with_HYBRID_where_default_is_OPAQUE_and_clean_up_file_and_messages", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "", + TargetAPI: gofeaturespb.GoFeatures_API_HYBRID, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test1; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + + } + + message Nested2 {} + + map<string, bool> map_field = 10; +} +`, + }, + { + input: "../../../testdata/flag_edition_test1_go_proto/flag_edition_test1.proto", + desc: "replace_file_option_with_HYBRID_where_default_is_OPAQUE_and_skip_cleanup", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + TargetAPI: gofeaturespb.GoFeatures_API_HYBRID, + SkipCleanup: true, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test1; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map<string, bool> map_field = 10; +} +`, + }, + { + input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto", + desc: "insert_file_option_OPEN_where_default_is_OPEN_and_clean_up", + task: setapi.Task{ + Path: "google/some.proto", + TargetAPI: gofeaturespb.GoFeatures_API_OPEN, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test2; + +import "google/protobuf/go_features.proto"; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + option + /*multi-line*/ + features.(pb.go) + .api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map<string, bool> map_field = 10; +} +`, + }, + { + input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto", + desc: "insert_message_M2_option_OPEN_using_fully-qualified_name_and_clean_up", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test2.M2", + TargetAPI: gofeaturespb.GoFeatures_API_OPEN, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test2; + +import "google/protobuf/go_features.proto"; + +message M1 { + + + message Nested1 { + option + /*multi-line*/ + features.(pb.go) + .api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { +option features.(pb.go).api_level = API_OPEN; + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 { +option features.(pb.go).api_level = API_OPAQUE;} + + map<string, bool> map_field = 10; +} +`, + }, + { + input: "../../../testdata/flag_edition_test5_go_proto/flag_edition_test5.proto", + desc: "inserting_message_M1_option_OPAQUE_works_with_leading_comment", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test5.M1", + TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test5; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_HYBRID; + +message M1 { +option features.(pb.go).api_level = API_OPAQUE; + // Leading comment on message should not be misplaced + // when API flag is inserted. + int32 int_field = 1; +} + +message M2 { + /** + * Leading block comment on message should not be misplaced + * when API flag is inserted. + */ + int32 int_field = 1; +} +`, + }, + { + input: "../../../testdata/flag_edition_test5_go_proto/flag_edition_test5.proto", + desc: "inserting_message_M1_option_OPAQUE_works_with_leading_block_comment", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "net.proto2.go.open2opaque.testdata.flag_edition_test5.M2", + TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test5; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + // Leading comment on message should not be misplaced + // when API flag is inserted. + int32 int_field = 1; +} + +message M2 { +option features.(pb.go).api_level = API_OPAQUE; + /** + * Leading block comment on message should not be misplaced + * when API flag is inserted. + */ + int32 int_field = 1; +} +`, + }, + { + input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto", + desc: "delete_message_M1.Nested1_option_to_make_OPAQUE_and_skip_cleanup", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "M1.Nested1", + TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + SkipCleanup: true, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test2; + +import "google/protobuf/go_features.proto"; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + + + message Nested2 { +option features.(pb.go).api_level = API_HYBRID;} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map<string, bool> map_field = 10; +} +`, + }, + { + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + desc: "leading_comments_exempt_file-level_API_flag_changes_and_cleanup", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test3; + +import "google/protobuf/go_features.proto"; + +// Leading comment exempts api_level from modifications. +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + // Exemption on message level. + option features.(pb.go).api_level = API_HYBRID; + + message Nested1 { + // Exemption on nested-message level. + option features.(pb.go).api_level = API_OPAQUE; + } +} + +message M2 { + +} +`, + }, + { + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + desc: "leading_comments_exempt_message_API_flag_changes_and_cleanup", + task: setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: "M1.Nested1", + TargetAPI: gofeaturespb.GoFeatures_API_OPEN, + }, + want: copyrightHeader + `edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test3; + +import "google/protobuf/go_features.proto"; + +// Leading comment exempts api_level from modifications. +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + // Exemption on message level. + option features.(pb.go).api_level = API_HYBRID; + + message Nested1 { + // Exemption on nested-message level. + option features.(pb.go).api_level = API_OPAQUE; + } +} + +message M2 { + +} +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + var err error + tc.task.Content, err = os.ReadFile(tc.input) + if err != nil { + t.Fatalf("runfiles.ReadFile: %v", err) + } + ctx := context.Background() + contentClone := slices.Clone(tc.task.Content) + got, err := setapi.Process(ctx, tc.task, "cat") + if err != nil { + t.Fatalf("setapi.Process: %v", err) + } + if !slices.Equal(tc.task.Content, contentClone) { + t.Fatalf("setapi.Process modified Task.Content") + } + if diff := cmp.Diff(tc.want, string(got)); diff != "" { + t.Fatalf("diff setapi.Process (-want +got):\n%v\n", diff) + } + }) + } +} + +func TestErrors(t *testing.T) { + testcases := []struct { + desc string + input string + symbol string + errorOnExempt bool + wantErr bool + }{ + { + desc: "error_for_unknown_symbol", + input: "../../../testdata/flag_edition_test2_go_proto/flag_edition_test2.proto", + symbol: "UNKNOWN", + wantErr: true, + }, + { + desc: "error_for_file-level_flag_with_leading_comment_with_errorOnExempt", + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + errorOnExempt: true, + wantErr: true, + }, + { + desc: "no_error_for_file-level_flag_with_leading_comment_without_errorOnExempt", + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + }, + { + desc: "error_for_message_flag_with_leading_comment_with_errorOnExempt", + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + symbol: "M1.Nested1", + errorOnExempt: true, + wantErr: true, + }, + { + desc: "error_for_message_flag_with_leading_comment_with_errorOnExempt_on_cleanup_for_M1", + input: "../../../testdata/flag_edition_test3_go_proto/flag_edition_test3.proto", + symbol: "M1", + errorOnExempt: true, + wantErr: true, + }, + } + for _, tc := range testcases { + t.Run(tc.desc, func(t *testing.T) { + task := setapi.Task{ + Path: "testonly-opaque-default-dummy.proto", + Symbol: tc.symbol, + TargetAPI: gofeaturespb.GoFeatures_API_OPEN, + ErrorOnExempt: tc.errorOnExempt, + } + var err error + task.Content, err = os.ReadFile(tc.input) + if err != nil { + t.Fatalf("runfiles.ReadFile: %v", err) + } + ctx := context.Background() + _, err = setapi.Process(ctx, task, "cat") + gotErr := err != nil + if gotErr != tc.wantErr { + t.Fatalf("setapi.Process: got err=%q, wantErr=%v", err, tc.wantErr) + } + }) + } +} + +const filenameWithOpaqueDefault = "testonly-opaque-default-dummy.proto" + +func TestSetFileAPI(t *testing.T) { + testCases := []struct { + name string + protoIn string + targetAPI gofeaturespb.GoFeatures_APILevel + skipCleanup bool + errorOnExempt bool + protoWant string + wantErr bool + }{ + { + name: "edition_is_default_opaque_not_explicit__do_nothing", + protoIn: ` +edition = "2023"; +package pkg; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message M{} +`, + }, + + { + name: "edition_is_default_opaque_explicit__remove_flag_and_eol_comment", + protoIn: ` +edition = "2023"; +package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message M{} +`, + }, + + { + name: "edition_is_default_opaque_explicit__skip_cleanup_dont_remove", + protoIn: ` +edition = "2023"; +package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + skipCleanup: true, + protoWant: ` +edition = "2023"; +package pkg;option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment +message M{} +`, + }, + + { + name: "edition_is_hybrid_explicit_want_opaque__remove_flag", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; + +message M{} +`, + }, + + { + name: "edition_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +package pkg; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_no_pkg_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_other_option_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +package pkg; +option java_package = "foo"; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +option java_package = "foo"; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_import_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +package pkg; +import "foo.proto"; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +import "foo.proto"; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +package pkg; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_no_pkg_is_default_opaque_not_explicit_want_hybrid__insert_flag", + protoIn: ` +edition = "2023"; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_is_open_explicit_want_hybrid__replace_flag", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_OPEN; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_is_opaque_explicit_want_hybrid__replace_flag", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_OPAQUE; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "edition_is_open_explicit_want_open__do_nothing", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_OPEN; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + protoWant: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_OPEN; +message M{} +`, + }, + + { + name: "leading_comment_prevents_replacement_with_errorOnExempt", + protoIn: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + errorOnExempt: true, + wantErr: true, + }, + + { + name: "leading_comment_prevents_replacement_without_errorOnExempt", + protoIn: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + protoWant: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + }, + + { + name: "leading_comment_prevents_deletion_with_errorOnExempt", + protoIn: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_OPAQUE; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + errorOnExempt: true, + wantErr: true, + }, + + { + name: "leading_comment_prevents_deletion_without_errorOnExempt", + protoIn: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_OPAQUE; +message M{} +`, + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +// leading comment +option features.(pb.go).api_level = API_OPAQUE; +message M{} +`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + task := setapi.Task{ + Path: filenameWithOpaqueDefault, + Content: []byte(tc.protoIn), + TargetAPI: tc.targetAPI, + SkipCleanup: tc.skipCleanup, + ErrorOnExempt: tc.errorOnExempt, + } + contentClone := slices.Clone(task.Content) + protoGot, err := setapi.Process(context.Background(), task, "cat") + switch { + case err != nil && tc.wantErr: + return + case err == nil && tc.wantErr: + t.Fatalf("wanted error but got nil") + case err != nil && !tc.wantErr: + t.Fatalf("didn't want error but got: %v", err) + } + if !slices.Equal(task.Content, contentClone) { + t.Fatalf("setapi.Process modified Task.Content") + } + if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" { + t.Errorf("setFileAPI: diff (-want +got):\n%v\n", diff) + } + }) + } +} + +func TestSetMsgAPI(t *testing.T) { + testCases := []struct { + name string + protoIn string + msgName string + targetAPI gofeaturespb.GoFeatures_APILevel + protoWant string + skipCleanup bool + wantErr bool + wantErrorLeadingCommentPreventsEdit bool + }{ + { + name: "edition_file_default_opaque__message_non_explicit_opaque_set_to_opaque__do_nothing", + protoIn: ` +edition = "2023"; +package pkg; +message M{ +string s = 1; +} +`, + msgName: "M", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message M{ +string s = 1; +} +`, + }, + + { + name: "edition_file_default_opaque__message_explicit_opaque_set_to_opaque__remove", + protoIn: ` +edition = "2023"; +package pkg; +message M{ +option features.(pb.go).api_level = API_OPAQUE; +string s = 1; +} +`, + msgName: "M", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message M{ + +string s = 1; +} +`, + }, + + { + name: "edition_file_default_opaque__fully_qualified_message_explicit_opaque_set_to_opaque__remove", + protoIn: ` +edition = "2023"; +package pkg; +message M{ +option features.(pb.go).api_level = API_OPAQUE; +string s = 1; +} +`, + msgName: "pkg.M", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message M{ + +string s = 1; +} +`, + }, + + { + name: "edition_file_default_opaque__message_explicit_opaque_set_to_opaque__skip_cleanup_dont_remove", + protoIn: ` +edition = "2023"; +package pkg; +message M{ +option features.(pb.go).api_level = API_OPAQUE; +string s = 1; +} +`, + msgName: "M", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + skipCleanup: true, + protoWant: ` +edition = "2023"; +package pkg; +message M{ +option features.(pb.go).api_level = API_OPAQUE; +string s = 1; +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_opaque__remove", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_hybrid__do_nothing", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_HYBRID, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + + string s = 1; + } + } +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_open__replace", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_OPEN; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_opaque__remove_and_fix_children", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + message A2{ + string s = 1; + message A3 { + string s = 1; + } + } + } +} +message B{ +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + + string s = 1; + message A2{ +option features.(pb.go).api_level = API_HYBRID; + string s = 1; + message A3 { + string s = 1; + } + } + } +} +message B{ +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_open__replace_and_fix_children", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + message A2{ + string s = 1; + message A3 { + string s = 1; + } + } + } +} +message B{ +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + option features.(pb.go).api_level = API_OPEN; + string s = 1; + message A2{ +option features.(pb.go).api_level = API_HYBRID; + string s = 1; + message A3 { + string s = 1; + } + } + } +} +message B{ +} +`, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_opaque__removal_prevented_by_leading_comment", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + // leading comment prevents removal + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + wantErr: true, + wantErrorLeadingCommentPreventsEdit: true, + }, + + { + name: "edition_file_default_opaque__message_hybrid_set_to_open__replacement_prevented_by_leading_comment", + protoIn: ` +edition = "2023"; +package pkg; +message A{ + message A1{ + // leading comment prevents replacement + option features.(pb.go).api_level = API_HYBRID; // end-of-line comment + string s = 1; + message A2{ + option features.(pb.go).api_level = API_HYBRID; + string s = 1; + } + } +} +`, + msgName: "A.A1", + targetAPI: gofeaturespb.GoFeatures_API_OPEN, + wantErr: true, + wantErrorLeadingCommentPreventsEdit: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + task := setapi.Task{ + Path: filenameWithOpaqueDefault, + Content: []byte(tc.protoIn), + Symbol: tc.msgName, + TargetAPI: tc.targetAPI, + SkipCleanup: tc.skipCleanup, + ErrorOnExempt: true, + } + protoGot, err := setapi.Process(context.Background(), task, "cat") + switch { + case err != nil && tc.wantErr: + if tc.wantErrorLeadingCommentPreventsEdit && !errors.Is(err, setapi.ErrLeadingCommentPreventsEdit) { + t.Fatalf("wanted wantErrorLeadingCommentPreventsEdit but got: %v", err) + } + return + case err == nil && tc.wantErr: + t.Fatalf("wanted error but got nil") + case err != nil && !tc.wantErr: + t.Fatalf("didn't want error but got: %v", err) + } + if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" { + t.Errorf("setMsgAPI: diff (-want +got):\n%v\n", diff) + } + }) + } +} + +func TestCleanup(t *testing.T) { + testCases := []struct { + name string + protoIn string + protoWant string + }{ + { + name: "edition_is_default_opaque_with_explicit_opaque__remove", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment +message M{} +`, + protoWant: ` +edition = "2023"; +package pkg; + +message M{} +`, + }, + + { + name: "edition_is_default_opaque_some_messages_opaque__remove", + protoIn: ` +edition = "2023"; +package pkg; +message A{ +option features.(pb.go).api_level = API_OPAQUE; // end-of-line comment +message A1{ +option features.(pb.go).api_level = API_OPEN; // end-of-line comment +message A2{ +option features.(pb.go).api_level = API_OPAQUE; +} +} +} +message B{ +// leading comment +option features.(pb.go).api_level = API_OPAQUE; +} +`, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + +message A1{ +option features.(pb.go).api_level = API_OPEN; // end-of-line comment +message A2{ +option features.(pb.go).api_level = API_OPAQUE; +} +} +} +message B{ +// leading comment +option features.(pb.go).api_level = API_OPAQUE; +} +`, + }, + + { + name: "edition_is_default_opaque_some_messages_opaque__remove_but_respect_api_inheritance", + protoIn: ` +edition = "2023"; +package pkg; +message A{ +option features.(pb.go).api_level = API_OPAQUE; +message A1{ +option features.(pb.go).api_level = API_OPEN; +message A2{ +// cannot clean up, because A2 inherits OPEN from A1 +option features.(pb.go).api_level = API_OPAQUE; +} +} +} +message B{ +option features.(pb.go).api_level = API_OPAQUE; +} +`, + protoWant: ` +edition = "2023"; +package pkg; +message A{ + +message A1{ +option features.(pb.go).api_level = API_OPEN; +message A2{ +// cannot clean up, because A2 inherits OPEN from A1 +option features.(pb.go).api_level = API_OPAQUE; +} +} +} +message B{ + +} +`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + task := setapi.Task{ + Path: filenameWithOpaqueDefault, + Content: []byte(tc.protoIn), + TargetAPI: gofeaturespb.GoFeatures_API_OPAQUE, + } + contentClone := slices.Clone(task.Content) + protoGot, err := setapi.Process(context.Background(), task, "cat") + if err != nil { + t.Fatal(err) + } + if !slices.Equal(task.Content, contentClone) { + t.Fatalf("setapi.Process modified Task.Content") + } + if diff := cmp.Diff(tc.protoWant, string(protoGot)); diff != "" { + t.Errorf("setMsgAPI: diff (-want +got):\n%v\n", diff) + } + }) + } +}
diff --git a/internal/o2o/statsutil/statsutil.go b/internal/o2o/statsutil/statsutil.go new file mode 100644 index 0000000..f492c31 --- /dev/null +++ b/internal/o2o/statsutil/statsutil.go
@@ -0,0 +1,30 @@ +// 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 statsutil provides functions for working with the open2opaque stats +// proto. +package statsutil + +import ( + "strings" + + statspb "google.golang.org/open2opaque/internal/dashboard" +) + +// ShortAndLongNameFrom takes a long name +// and returns a combined short and long name statspb.Type. +func ShortAndLongNameFrom(long string) *statspb.Type { + short := long + if idx := strings.LastIndex(long, "."); idx >= 0 { + short = long[idx+1:] + stars := strings.LastIndex(long, "*") + if stars != -1 { + short = long[:stars+1] + short + } + } + return &statspb.Type{ + LongName: long, + ShortName: short, + } +}
diff --git a/internal/o2o/statsutil/statsutil_test.go b/internal/o2o/statsutil/statsutil_test.go new file mode 100644 index 0000000..93541e8 --- /dev/null +++ b/internal/o2o/statsutil/statsutil_test.go
@@ -0,0 +1,33 @@ +// 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 statsutil_test + +import ( + "testing" + + "google.golang.org/open2opaque/internal/o2o/statsutil" +) + +func TestShortAndLongNameFrom(t *testing.T) { + for _, tt := range []struct { + long string + wantLong string + wantShort string + }{ + { + long: "google.golang.org/open2opaque/random_go_proto.MyMessage", + wantLong: "google.golang.org/open2opaque/random_go_proto.MyMessage", + wantShort: "MyMessage", + }, + } { + typ := statsutil.ShortAndLongNameFrom(tt.long) + if got := typ.GetLongName(); got != tt.wantLong { + t.Errorf("ShortAndLongNameFrom(%s): unexpected long name: got %q, want %q", tt.long, got, tt.wantLong) + } + if got := typ.GetShortName(); got != tt.wantShort { + t.Errorf("ShortAndLongNameFrom(%s): unexpected short name: got %q, want %q", tt.long, got, tt.wantShort) + } + } +}
diff --git a/internal/o2o/syncset/syncset.go b/internal/o2o/syncset/syncset.go new file mode 100644 index 0000000..7a11711 --- /dev/null +++ b/internal/o2o/syncset/syncset.go
@@ -0,0 +1,34 @@ +// 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 syncset implements sets that can be safely accessed concurrently. +package syncset + +import "sync" + +// New returns a new, empty set. +func New() *Set { + return &Set{ + set: map[string]struct{}{}, + } +} + +// Set is a set of strings. +type Set struct { + mu sync.Mutex + set map[string]struct{} +} + +// Add adds the value v to the set and returns true if it wasn't in the set +// before. It returns false otherwise. +func (s *Set) Add(v string) bool { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.set[v] + if !ok { + s.set[v] = struct{}{} + return true + } + return false +}
diff --git a/internal/o2o/syncset/syncset_test.go b/internal/o2o/syncset/syncset_test.go new file mode 100644 index 0000000..bdee154 --- /dev/null +++ b/internal/o2o/syncset/syncset_test.go
@@ -0,0 +1,36 @@ +// 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 syncset + +import ( + "sync" + "testing" +) + +func TestSyncset(t *testing.T) { + s := New() + if !s.Add("hello") { + t.Error("AddNew() returned false for an empty set") + } + if s.Add("hello") { + t.Error("AddNew('hello') returned true for a set containing 'hello'") + } + if !s.Add("world") { + t.Error("AddNew('world') returned false for a set without 'world'") + } +} + +func TestNoRace(t *testing.T) { + var wg sync.WaitGroup + s := New() + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + s.Add("hello") + }() + } + wg.Wait() +}
diff --git a/internal/o2o/version/version.go b/internal/o2o/version/version.go new file mode 100644 index 0000000..a35e817 --- /dev/null +++ b/internal/o2o/version/version.go
@@ -0,0 +1,86 @@ +package version + +import ( + "context" + "fmt" + "runtime/debug" + "time" + + "flag" + "github.com/google/subcommands" +) + +// Cmd implements the version subcommand of the open2opaque tool. +type Cmd struct{} + +// Name implements subcommand.Command. +func (*Cmd) Name() string { return "version" } + +// Synopsis implements subcommand.Command. +func (*Cmd) Synopsis() string { return "print tool version" } + +// Usage implements subcommand.Command. +func (*Cmd) Usage() string { return `Usage: open2opaque version` } + +// SetFlags implements subcommand.Command. +func (*Cmd) SetFlags(*flag.FlagSet) {} + +func synthesizeVersion(info *debug.BuildInfo) string { + const fallback = "(devel)" + settings := make(map[string]string) + for _, s := range info.Settings { + settings[s.Key] = s.Value + } + + rev, ok := settings["vcs.revision"] + if !ok { + return fallback + } + + commitTime, err := time.Parse(time.RFC3339Nano, settings["vcs.time"]) + if err != nil { + return fallback + } + + modifiedSuffix := "" + if settings["vcs.modified"] == "true" { + modifiedSuffix += "+dirty" + } + + // Go pseudo versions use 12 hex digits. + if len(rev) > 12 { + rev = rev[:12] + } + + // Copied from x/mod/module/pseudo.go + const PseudoVersionTimestampFormat = "20060102150405" + + return fmt.Sprintf("v?.?.?-%s-%s%s", + commitTime.UTC().Format(PseudoVersionTimestampFormat), + rev, + modifiedSuffix) +} + +// Execute implements subcommand.Command. +func (cmd *Cmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus { + info, ok := debug.ReadBuildInfo() + mainVersion := info.Main.Version + if !ok { + mainVersion = "<runtime/debug.ReadBuildInfo failed>" + } + // As of Go 1.24 (https://tip.golang.org/doc/go1.24#go-command), + // we get v0.0.0-20241211143045-0af77b971425+dirty for git builds. + if mainVersion == "(devel)" { + // Before Go 1.24, the main module version just contained "(devel)" when + // building from git. Try and find a git revision identifier. + mainVersion = synthesizeVersion(info) + } + fmt.Printf("open2opaque %s\n", mainVersion) + return subcommands.ExitSuccess +} + +// Command returns an initialized Cmd for registration with the subcommands +// package. +func Command() *Cmd { + return &Cmd{} +}
diff --git a/internal/o2o/wd/wd.go b/internal/o2o/wd/wd.go new file mode 100644 index 0000000..8e5dde3 --- /dev/null +++ b/internal/o2o/wd/wd.go
@@ -0,0 +1,11 @@ +// 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 wd deals with adjusting the working directory as needed +package wd + +func Adjust() (subdir string, _ error) { + + return "", nil +}
diff --git a/internal/protodetect/protodetect.go b/internal/protodetect/protodetect.go new file mode 100644 index 0000000..45ae144 --- /dev/null +++ b/internal/protodetect/protodetect.go
@@ -0,0 +1,90 @@ +// 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 protodetect provides function to identify the go_api_flag +// file and message options specified in the files. +package protodetect + +import ( + "fmt" + + "google.golang.org/protobuf/compiler/protogen" + "google.golang.org/protobuf/proto" + + pb "google.golang.org/open2opaque/internal/apiflagdata" + descpb "google.golang.org/protobuf/types/descriptorpb" + gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" + pluginpb "google.golang.org/protobuf/types/pluginpb" +) + +func fromFeatureToOld(apiLevel gofeaturespb.GoFeatures_APILevel) pb.GoAPI { + switch apiLevel { + case gofeaturespb.GoFeatures_API_OPEN: + return pb.GoAPI_OPEN_V1 + + case gofeaturespb.GoFeatures_API_HYBRID: + return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID + + case gofeaturespb.GoFeatures_API_OPAQUE: + return pb.GoAPI_OPAQUE_V0 + + default: + panic(fmt.Sprintf("unknown apilevel %v", apiLevel)) + } +} + +func apiLevelForDescriptor(fd *descpb.FileDescriptorProto) gofeaturespb.GoFeatures_APILevel { + return apiLevelForDescriptorOpts(protogen.Options{}, fd) +} + +func apiLevelForDescriptorOpts(opts protogen.Options, fd *descpb.FileDescriptorProto) gofeaturespb.GoFeatures_APILevel { + fopts := fd.Options + if fopts == nil { + fopts = &descpb.FileOptions{} + fd.Options = fopts + } + fopts.GoPackage = proto.String("dummy/package") + + // Determine the API level by querying protogen using a stub plugin request. + plugin, err := opts.New(&pluginpb.CodeGeneratorRequest{ + ProtoFile: []*descpb.FileDescriptorProto{fd}, + }) + if err != nil { + panic(err) + } + if got, want := len(plugin.Files), 1; got != want { + panic(fmt.Sprintf("protogen returned %d plugin.Files entries, expected %d", got, want)) + } + return plugin.Files[0].APILevel +} + +// DefaultFileLevel returns the default go_api_flag option for given path. +func DefaultFileLevel(path string) gofeaturespb.GoFeatures_APILevel { + if path == "testonly-opaque-default-dummy.proto" { + // Magic filename to test the edition 2024+ behavior (Opaque API by + // default) from setapi_test.go + return gofeaturespb.GoFeatures_API_OPAQUE + } + + fd := &descpb.FileDescriptorProto{Name: proto.String(path)} + return apiLevelForDescriptor(fd) +} + +// MapGoAPIFlag maps current and old values of the go API flag to current values. +func MapGoAPIFlag(val string) (pb.GoAPI, error) { + // Old go_api_flag values are still used here in order to parse proto files from + // older /google_src/files/... paths. + // LINT.IfChange(go_api_flag_parsing) + switch val { + case "OPEN_V1": + return pb.GoAPI_OPEN_V1, nil + case "OPEN_TO_OPAQUE_HYBRID": + return pb.GoAPI_OPEN_TO_OPAQUE_HYBRID, nil + case "OPAQUE_V0": + return pb.GoAPI_OPAQUE_V0, nil + default: + return pb.GoAPI_INVALID, fmt.Errorf("invalid value: %q", val) + } + // LINT.ThenChange() +}
diff --git a/internal/protodetecttypes/protodetecttypes.go b/internal/protodetecttypes/protodetecttypes.go new file mode 100644 index 0000000..f02a2d7 --- /dev/null +++ b/internal/protodetecttypes/protodetecttypes.go
@@ -0,0 +1,65 @@ +// 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 protodetecttypes identifies static types (go/types, typically +// obtained at build time) generated by protoc-gen-go. The protodetectreflect +// package is the counterpart for dynamic types (reflect, obtained at runtime). +package protodetecttypes + +import ( + "go/types" + "reflect" + "strings" +) + +// Type represents a go/types.Type obtained at build time. +type Type struct{ T types.Type } + +// IsMessage reports whether t is a generated message. +func (t Type) IsMessage() bool { + return t.MessageAPI() != Invalid +} + +// MessageAPI determines the API of the generated message. +func (t Type) MessageAPI() MessageAPI { + if t, ok := t.T.Underlying().(*types.Struct); ok && t.NumFields() > 0 { + return determineAPI(reflect.StructTag(t.Tag(0))) + } + return Invalid +} + +// MessageAPI determines which API is used for the generated message type. +type MessageAPI int + +const ( + // Invalid indicates the specified type is not a proto message. + Invalid MessageAPI = iota + + // OpenAPI indicates a message generated in the open struct API, + // where the message representation is a Go struct with exported fields. + OpenAPI + + // HybridAPI indicates a message generated in the hybrid API, + // where the message has properties of both OpenAPI and OpaqueAPI. + HybridAPI + + // OpaqueAPI indicates a message generated in the opaque API, + // where the message can only be interacted with through accessor methods. + OpaqueAPI +) + +// DetermineAPI determines the message API from a magic struct tag that +// protoc-gen-go emits for all messages. +func determineAPI(tag reflect.StructTag) MessageAPI { + switch s := tag.Get("protogen"); { + case strings.HasPrefix(s, "open."): + return OpenAPI + case strings.HasPrefix(s, "hybrid."): + return HybridAPI + case strings.HasPrefix(s, "opaque."): + return OpaqueAPI + default: + return Invalid + } +}
diff --git a/internal/protoparse/protoparse.go b/internal/protoparse/protoparse.go new file mode 100644 index 0000000..892a0b9 --- /dev/null +++ b/internal/protoparse/protoparse.go
@@ -0,0 +1,385 @@ +// 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 protoparse provides function to parse proto source files and identify the go_api_flag +// file and message options specified in the files. +package protoparse + +import ( + "bytes" + "fmt" + "slices" + "strings" + + "github.com/jhump/protoreflect/desc/protoparse" + "google.golang.org/open2opaque/internal/protodetect" + "google.golang.org/protobuf/proto" + + pb "google.golang.org/open2opaque/internal/apiflagdata" + descpb "google.golang.org/protobuf/types/descriptorpb" + gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" +) + +// TextRange describes a location in a proto file. Please note that the column +// indices are code-point indices, not byte indices. +type TextRange struct { + BeginLine int + BeginCol int + EndLine int + EndCol int +} + +// SpanToTextRange converts a proto2.SourceCodeInfo.Location.span to a +// TextRange. +func SpanToTextRange(span []int32) TextRange { + if len(span) < 3 && len(span) > 4 { + panic(fmt.Sprintf("input %v isn't a proto2.SourceCodeInfo.Location.span", span)) + } + if len(span) == 3 { + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1209 + return TextRange{ + BeginLine: int(span[0]), + BeginCol: int(span[1]), + EndLine: int(span[0]), + EndCol: int(span[2]), + } + } + + return TextRange{ + BeginLine: int(span[0]), + BeginCol: int(span[1]), + EndLine: int(span[2]), + EndCol: int(span[3]), + } +} + +// ToByteRange converts line and column information to a byte range. +func (tr TextRange) ToByteRange(content []byte) (beginByte, endByte int, err error) { + if tr.EndLine < tr.BeginLine { + return -1, -1, fmt.Errorf("EndLine %d < BeginLine %d", tr.EndLine, tr.BeginLine) + } + if tr.EndLine == tr.BeginLine && tr.EndCol < tr.BeginCol { + return -1, -1, fmt.Errorf("EndCol %d < BeginCol %d in the same line", tr.EndCol, tr.BeginCol) + } + { + lines := bytes.Split(content, []byte{'\n'})[tr.BeginLine : tr.EndLine+1] + if bytes.Contains(bytes.Join(lines, []byte{'\n'}), []byte{'\t'}) { + // The parser deals with tabs in a complicated manner, see + // https://github.com/bufbuild/protocompile/blob/c91057b816eb7f827dfa83ff5288b74ead9d4fe5/ast/file_info.go#L362-L364. + // We currently don't support this. + return -1, -1, fmt.Errorf("line range contains tabs") + } + } + beginByte = -1 + endByte = -1 + from := 0 + newlineIdx := 0 + lineNumber := 0 + for newlineIdx >= 0 { + newlineIdx := bytes.IndexByte(content[from:], '\n') + to := from + newlineIdx + 1 + if newlineIdx == -1 { + to = len(content) - 1 + } + lineRunes := bytes.Runes(content[from:to]) + if lineNumber == tr.BeginLine { + if tr.BeginCol > len(lineRunes) { + return -1, -1, fmt.Errorf("BeginCol %d is out of range, line is %q", tr.BeginCol, string(lineRunes)) + } + beginByte = from + len(string(lineRunes[:tr.BeginCol])) + } + if lineNumber == tr.EndLine { + if tr.EndCol > len(lineRunes) { + return -1, -1, fmt.Errorf("EndCol %d is out of range, line is %q", tr.EndCol, string(lineRunes)) + } + endByte = from + len(string(lineRunes[:tr.EndCol])) + break + } + + from = to + lineNumber++ + } + if endByte == -1 { + return -1, -1, fmt.Errorf("EndLine %d is out of range, number lines is %d", tr, lineNumber) + } + return beginByte, endByte, nil +} + +// APIInfo contains information about an explicit API flag definition. +type APIInfo struct { + TextRange TextRange + HasLeadingComment bool + path []int32 +} + +// FileOpt contains the Go API level info for a file along with other proto +// info. +type FileOpt struct { + // File name containing relative path + File string + // Proto package name. + Package string + // Go API level. This can be an implicit value via default. + GoAPI gofeaturespb.GoFeatures_APILevel + // Whether go_api_flag option is explicitly set in proto file or not. + IsExplicit bool + // APIInfo is nil if IsExplicit is false. + APIInfo *APIInfo + // Options of messages defined at the file level. Nested messages are stored + // as their children. + MessageOpts []*MessageOpt + // SourceCodeInfo is set if parsed results includes it. + SourceCodeInfo *descpb.SourceCodeInfo + // Proto syntax: "proto2", "proto3", "editions", or "editions_go_api_flag". + // The latter is set for editions protos that use the old go_api_flag + // explicitly. + Syntax string +} + +// MessageOpt contains the Go API level info for a message. +type MessageOpt struct { + // Proto message name. Includes parent name if nested, e.g. A.B for message + // B that is defined in body of A. + Message string + // Go API level. This can be an implicit value via file option or in case of + // editions features via the parent message. + GoAPI gofeaturespb.GoFeatures_APILevel + // Whether go_api_flag option is explicitly set in proto message or not. + IsExplicit bool + // APIInfo is nil if IsExplicit is false. + APIInfo *APIInfo + // FileDescriptorProto.source_code_info.location.path of this message: + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L1202 + // Example: The 1st nested message of the 6th message in the file is in path + // [4, 5, 3, 0]; 4 is the field number of FileDescriptorProto.message_type, 5 + // is the index for the 6th message, 3 is DescriptorProto.nested_type, 0 is + // the index for the first nested message. + LocPath []int32 + // Options of the parent message. If this is e.g. the message B which is + // defined in the body of message A, then A is the parent. Parent is nil for + // messages defined at the file level, i.e. non-nested messages. + Parent *MessageOpt + // Options of the child messages. If this is e.g. message A and messages + // B and C are defined in the body of message A, then B and C are the + // children. + Children []*MessageOpt +} + +// Parser parses proto source files for go_api_flag values. +type Parser struct { + parser protoparse.Parser +} + +// NewParser constructs a Parser with default file accessor. +func NewParser() *Parser { + return &Parser{protoparse.Parser{ + InterpretOptionsInUnlinkedFiles: true, + IncludeSourceCodeInfo: true, + }} +} + +// NewParserWithAccessor constructs a Parser with a custom file accessor. +func NewParserWithAccessor(acc protoparse.FileAccessor) *Parser { + return &Parser{protoparse.Parser{ + InterpretOptionsInUnlinkedFiles: true, + IncludeSourceCodeInfo: true, + Accessor: acc, + }} +} + +func fromOldToFeature(apiLevel pb.GoAPI) (gofeaturespb.GoFeatures_APILevel, error) { + switch apiLevel { + case pb.GoAPI_OPEN_V1: + return gofeaturespb.GoFeatures_API_OPEN, nil + + case pb.GoAPI_OPEN_TO_OPAQUE_HYBRID: + return gofeaturespb.GoFeatures_API_HYBRID, nil + + case pb.GoAPI_OPAQUE_V0: + return gofeaturespb.GoFeatures_API_OPAQUE, nil + + default: + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, fmt.Errorf("unknown apilevel %v", apiLevel) + } +} + +func uninterpretedGoAPIFeature(opts []*descpb.UninterpretedOption) (gofeaturespb.GoFeatures_APILevel, int) { + for i, opt := range opts { + nameParts := opt.GetName() + if len(nameParts) != 3 || + nameParts[0].GetNamePart() != "features" || + nameParts[1].GetNamePart() != "pb.go" || + nameParts[2].GetNamePart() != "api_level" { + continue + } + v := string(opt.GetIdentifierValue()) + switch v { + case "API_OPEN": + return gofeaturespb.GoFeatures_API_OPEN, i + case "API_HYBRID": + return gofeaturespb.GoFeatures_API_HYBRID, i + case "API_OPAQUE": + return gofeaturespb.GoFeatures_API_OPAQUE, i + default: + panic(fmt.Sprintf("unknown features.(pb.go).api_level value %v", v)) + } + } + return gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED, -1 +} + +func fileGoAPIEditions(desc *descpb.FileDescriptorProto) (gofeaturespb.GoFeatures_APILevel, bool, []int32, error) { + if proto.HasExtension(desc.GetOptions().GetFeatures(), gofeaturespb.E_Go) { + panic("unimplemented: Go extension features are fully parsed in file options") + } + api, idx := uninterpretedGoAPIFeature(desc.GetOptions().GetUninterpretedOption()) + if api == gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED { + return protodetect.DefaultFileLevel(desc.GetName()), false, nil, nil + } + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L122 + fileOptionsField = 8 + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L553 + uninterpretedOptionField = 999 + ) + return api, true, []int32{fileOptionsField, uninterpretedOptionField, int32(idx)}, nil +} + +func traverseMsgOpts(opt *MessageOpt, f func(*MessageOpt)) { + f(opt) + for _, c := range opt.Children { + traverseMsgOpts(c, f) + } +} + +// ParseFile reads the given proto source file name +// and determines the API level. If skipMessages is set to +// true, return value will have nil MessageOpts field. +func (p *Parser) ParseFile(name string, skipMessages bool) (*FileOpt, error) { + descs, err := p.parser.ParseFilesButDoNotLink(name) + if err != nil { + return nil, fmt.Errorf("error reading file %s: %w", name, err) + } + desc := descs[0] + + var fileAPI gofeaturespb.GoFeatures_APILevel + var explicit bool + var sciPath []int32 + + syntax := desc.GetSyntax() + fileAPI, explicit, sciPath, err = fileGoAPIEditions(desc) + if err != nil { + return nil, fmt.Errorf("fileGoAPIEditions: %v", err) + } + + var mopts []*MessageOpt + if !skipMessages { + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L117 + descriptorProtoField = 4 + ) + for i, m := range desc.GetMessageType() { + var mopt *MessageOpt + mopt = readMessagesEditions(m, fileAPI, "", []int32{descriptorProtoField, int32(i)}) + mopts = append(mopts, mopt) + } + } + var info *APIInfo + var allAPIInfos []*APIInfo + if explicit { + info = &APIInfo{path: sciPath} + allAPIInfos = append(allAPIInfos, info) + } + + for _, mLoop := range mopts { + traverseMsgOpts(mLoop, func(m *MessageOpt) { + allAPIInfos = append(allAPIInfos, m.APIInfo) + }) + } + // The APIInfos only contain SourceCodeInfo paths so far. Now, fill in the + // line and column information by directly modifying the pointee text ranges + // in allTextRanges. + fillAPIInfos(allAPIInfos, desc.GetSourceCodeInfo()) + + return &FileOpt{ + File: name, + Package: desc.GetPackage(), + GoAPI: fileAPI, + IsExplicit: explicit, + APIInfo: info, + MessageOpts: mopts, + SourceCodeInfo: desc.GetSourceCodeInfo(), + Syntax: syntax, + }, nil +} + +func fillAPIInfos(infos []*APIInfo, info *descpb.SourceCodeInfo) { + m := make(map[string]*APIInfo) + for _, info := range infos { + if info != nil { + m[fmt.Sprint(info.path)] = info + } + } + for _, loc := range info.GetLocation() { + if info, ok := m[fmt.Sprint(loc.GetPath())]; ok { + info.TextRange = SpanToTextRange(loc.GetSpan()) + leading := strings.TrimSpace(loc.GetLeadingComments()) + switch { + default: + info.HasLeadingComment = leading != "" + } + } + } +} + +func readMessagesEditions(m *descpb.DescriptorProto, parentAPI gofeaturespb.GoFeatures_APILevel, namePrefix string, msgPath []int32) *MessageOpt { + if m.GetOptions().GetMapEntry() { + // Map-entry messages are auto-generated and their Go API level cannot + // be adjusted in the proto file. + return nil + } + name := m.GetName() + if namePrefix != "" { + name = namePrefix + "." + m.GetName() + } + + // If not set, default to parent value. This is the file API for a message + // at the file level or the API of the parent message for a nested message. + msgAPI := parentAPI + var info *APIInfo + var isSet bool + if api, idx := uninterpretedGoAPIFeature(m.GetOptions().GetUninterpretedOption()); api != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED { + msgAPI = api + isSet = true + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L160 + messageOptionsField = 7 + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L638 + uninterpretedOptionField = 999 + ) + info = &APIInfo{path: append(slices.Clone(msgPath), messageOptionsField, uninterpretedOptionField, int32(idx))} + } + + mopt := &MessageOpt{ + Message: name, + GoAPI: msgAPI, + IsExplicit: isSet, + APIInfo: info, + LocPath: slices.Clone(msgPath), + } + + for i, n := range m.GetNestedType() { + const ( + // https://github.com/protocolbuffers/protobuf/blob/v29.1/src/google/protobuf/descriptor.proto#L147 + nestedDescriptorProtoField = 3 + ) + // Pass msgAPI as parent API: edition features are inherited by nested messages. + nopt := readMessagesEditions(n, msgAPI, name, append(slices.Clone(msgPath), nestedDescriptorProtoField, int32(i))) + if nopt == nil { + continue + } + mopt.Children = append(mopt.Children, nopt) + nopt.Parent = mopt + } + return mopt +}
diff --git a/internal/protoparse/protoparse_test.go b/internal/protoparse/protoparse_test.go new file mode 100644 index 0000000..c77a84c --- /dev/null +++ b/internal/protoparse/protoparse_test.go
@@ -0,0 +1,315 @@ +// 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 protoparse_test + +import ( + "io" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/open2opaque/internal/protodetect" + "google.golang.org/open2opaque/internal/protoparse" + gofeaturespb "google.golang.org/protobuf/types/gofeaturespb" +) + +const filenameWithOpaqueDefault = "foo.proto" + +func TestTextRangeToByteRange(t *testing.T) { + testCases := []struct { + in string + textRange protoparse.TextRange + want string + }{ + { + in: "0123456789", + textRange: protoparse.TextRange{ + BeginLine: 0, + BeginCol: 2, + EndLine: 0, + EndCol: 6, + }, + want: "2345", + }, + { + in: `0Hello, 世界0 +1Hello, 世界1 +2Hello, 世界2 +3Hello, 世界3`, + textRange: protoparse.TextRange{ + BeginLine: 1, + BeginCol: 8, + EndLine: 3, + EndCol: 6, + }, + want: `世界1 +2Hello, 世界2 +3Hello`, + }, + } + for _, tc := range testCases { + from, to, err := tc.textRange.ToByteRange([]byte(tc.in)) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tc.want, string(tc.in[from:to])); diff != "" { + t.Errorf("TextRange.ToByteRange: diff (-want +got):\n%v\n", diff) + } + } +} + +func TestFileOpt(t *testing.T) { + testCases := []struct { + name string + protoIn string + wantOpt *protoparse.FileOpt + wantAPIStr string + wantHasLeadingComment bool + }{ + { + name: "proto3_default_opaque", + protoIn: ` +syntax = "proto3"; +package pkg; +message M{} +`, + wantOpt: &protoparse.FileOpt{ + File: filenameWithOpaqueDefault, + Package: "pkg", + GoAPI: protodetect.DefaultFileLevel("dummy.proto"), + IsExplicit: false, + Syntax: "proto3", + }, + }, + + { + name: "edition_default_opaque", + protoIn: ` +edition = "2023"; +package pkg; +message M{} +`, + wantOpt: &protoparse.FileOpt{ + File: filenameWithOpaqueDefault, + Package: "pkg", + GoAPI: protodetect.DefaultFileLevel("dummy.proto"), + IsExplicit: false, + Syntax: "editions", + }, + }, + + { + name: "edition_explicit_hybrid", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message M{} +`, + wantOpt: &protoparse.FileOpt{ + File: filenameWithOpaqueDefault, + Package: "pkg", + GoAPI: gofeaturespb.GoFeatures_API_HYBRID, + IsExplicit: true, + Syntax: "editions", + }, + wantAPIStr: "option features.(pb.go).api_level = API_HYBRID;", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(tc.protoIn)), nil + }) + got, err := parser.ParseFile(filenameWithOpaqueDefault, true) + if err != nil { + t.Fatal(err) + } + ingores := cmp.Options{ + cmpopts.IgnoreFields(protoparse.FileOpt{}, "APIInfo"), + cmpopts.IgnoreFields(protoparse.FileOpt{}, "SourceCodeInfo"), + cmpopts.IgnoreFields(protoparse.FileOpt{}, "MessageOpts"), + } + if diff := cmp.Diff(tc.wantOpt, got, ingores...); diff != "" { + t.Errorf("parser.ParseFile(): diff (-want +got):\n%v\n", diff) + } + + if !tc.wantOpt.IsExplicit { + // File doesn't explicitly define API flag, don't check content of + // APIInfo + return + } + if got.APIInfo == nil { + t.Fatal("API flag is set explicitly, but APIInfo is nil") + } + + if got, want := got.APIInfo.HasLeadingComment, tc.wantHasLeadingComment; got != want { + t.Fatalf("got APIInfo.HasLeadingComment %v, want %v", got, want) + } + + from, to, err := got.APIInfo.TextRange.ToByteRange([]byte(tc.protoIn)) + if err != nil { + t.Fatalf("TextRange.ToByteRange: %v", err) + } + gotAPIStr := string([]byte(tc.protoIn)[from:to]) + if diff := cmp.Diff(tc.wantAPIStr, gotAPIStr); diff != "" { + t.Errorf("API string: diff (-want +got):\n%v\n", diff) + } + }) + } +} + +func traverseMsg(opt *protoparse.MessageOpt, f func(*protoparse.MessageOpt)) { + f(opt) + for _, c := range opt.Children { + traverseMsg(c, f) + } +} + +func TestMessageOpt(t *testing.T) { + type msgInfo struct { + Opt *protoparse.MessageOpt + APIString string + HasLeadingComment bool + } + + testCases := []struct { + name string + protoIn string + want []msgInfo + }{ + + { + name: "edition_file_hybrid_message_one_msg_open", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message A{ + option features.(pb.go).api_level = API_OPEN; + message A1{} +} +`, + want: []msgInfo{ + { + Opt: &protoparse.MessageOpt{ + Message: "A", + GoAPI: gofeaturespb.GoFeatures_API_OPEN, + IsExplicit: true, + }, + APIString: `option features.(pb.go).api_level = API_OPEN;`, + }, + { + Opt: &protoparse.MessageOpt{ + Message: "A.A1", + // API level of parent message is inherited. + GoAPI: gofeaturespb.GoFeatures_API_OPEN, + IsExplicit: false, + }, + }, + }, + }, + + { + name: "edition_file_hybrid_message_one_msg_open_with_leading_comment", + protoIn: ` +edition = "2023"; +package pkg; +option features.(pb.go).api_level = API_HYBRID; +message A{ + // leading comment + option features.(pb.go).api_level = API_OPEN; + message A1{ + option features.(pb.go).api_level = API_OPAQUE; + } +} +`, + // https://github.com/bufbuild/protocompile/blob/main/ast/file_info.go#L362-L364 + want: []msgInfo{ + { + Opt: &protoparse.MessageOpt{ + Message: "A", + GoAPI: gofeaturespb.GoFeatures_API_OPEN, + IsExplicit: true, + }, + APIString: `option features.(pb.go).api_level = API_OPEN;`, + HasLeadingComment: true, + }, + { + Opt: &protoparse.MessageOpt{ + Message: "A.A1", + GoAPI: gofeaturespb.GoFeatures_API_OPAQUE, + IsExplicit: true, + }, + APIString: `option features.(pb.go).api_level = API_OPAQUE;`, + HasLeadingComment: false, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + parser := protoparse.NewParserWithAccessor(func(string) (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(tc.protoIn)), nil + }) + gotFopt, err := parser.ParseFile(filenameWithOpaqueDefault, false) + if err != nil { + t.Fatal(err) + } + + // Flatten the tree of message options and wrap into []msgInfo to allow + // easy comparison. Check tree structure below. + var gotInfo []msgInfo + for _, optLoop := range gotFopt.MessageOpts { + traverseMsg(optLoop, func(opt *protoparse.MessageOpt) { + info := msgInfo{Opt: opt} + if opt.IsExplicit { + if opt.APIInfo == nil { + t.Fatalf("API flag is set explicitly, but APIInfo of message %q is nil", opt.Message) + } + info.HasLeadingComment = opt.APIInfo.HasLeadingComment + from, to, err := opt.APIInfo.TextRange.ToByteRange([]byte(tc.protoIn)) + if err != nil { + t.Fatalf("TextRange.ToByteRange: %v", err) + } + info.APIString = string([]byte(tc.protoIn)[from:to]) + } + gotInfo = append(gotInfo, info) + }) + } + + ingores := cmp.Options{ + cmpopts.IgnoreFields(protoparse.MessageOpt{}, "APIInfo"), + cmpopts.IgnoreFields(protoparse.MessageOpt{}, "LocPath"), + cmpopts.IgnoreFields(protoparse.MessageOpt{}, "Parent"), + cmpopts.IgnoreFields(protoparse.MessageOpt{}, "Children"), + } + if diff := cmp.Diff(tc.want, gotInfo, ingores...); diff != "" { + t.Errorf("diff (-want +got):\n%v\n", diff) + } + + // Check the tree structure of the message options. If I'm called A.A1.A2, + // is my parent called A.A1? + for _, optOuter := range gotFopt.MessageOpts { + traverseMsg(optOuter, func(opt *protoparse.MessageOpt) { + parts := strings.Split(opt.Message, ".") + if len(parts) == 1 { + if opt.Parent != nil { + t.Errorf("parent pointer of top-level message %q isn't nil", opt.Message) + } + return + } + if opt.Parent == nil { + t.Errorf("parent pointer of nested message %q is nil", opt.Message) + } + if got, want := opt.Parent.Message, strings.Join(parts[:len(parts)-1], "."); got != want { + t.Errorf("got name of parent message %q, want %q", got, want) + } + }) + } + }) + } +}
diff --git a/open2opaque.go b/open2opaque.go new file mode 100644 index 0000000..bbc9444 --- /dev/null +++ b/open2opaque.go
@@ -0,0 +1,66 @@ +// 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. + +// The program open2opaque migrates Go code using Go Protobuf from the Open API +// to the Opaque API. +// +// See https://go.dev/blog/protobuf-opaque for context. +package main + +import ( + "context" + "fmt" + "io" + _ "net/http/pprof" + "os" + "path" + + "flag" + "github.com/google/subcommands" + "google.golang.org/open2opaque/internal/o2o/rewrite" + "google.golang.org/open2opaque/internal/o2o/setapi" + "google.golang.org/open2opaque/internal/o2o/version" +) + +const groupOther = "working with this tool" + +func registerVersion(commander *subcommands.Commander) { + commander.Register(version.Command(), groupOther) +} + +func main() { + ctx := context.Background() + + commander := subcommands.NewCommander(flag.CommandLine, path.Base(os.Args[0])) + + // Prepend general documentation before the regular help output. + defaultExplain := commander.Explain + commander.Explain = func(w io.Writer) { + fmt.Fprintf(w, "The open2opaque tool migrates Go packages from the open protobuf API to the opaque protobuf API.\n\n") + fmt.Fprintf(w, "See http://godoc/3/third_party/golang/google_golang_org/open2opaque/v/v0/open2opaque for documentation.\n\n") + defaultExplain(w) + } + + // Comes last in the help output (alphabetically) + commander.Register(commander.HelpCommand(), groupOther) + commander.Register(commander.FlagsCommand(), groupOther) + registerVersion(commander) + + // Comes first in the help output (alphabetically) + const groupRewrite = "automatically rewriting Go code" + commander.Register(rewrite.Command(), groupRewrite) + + const groupFlag = "managing go_api_flag" + commander.Register(setapi.Command(), groupFlag) + + flag.Usage = func() { + commander.HelpCommand().Execute(ctx, flag.CommandLine) + } + + flag.Parse() + + code := int(commander.Execute(ctx)) + logFlush() + os.Exit(code) +}
diff --git a/open2opaque_flush.go b/open2opaque_flush.go new file mode 100644 index 0000000..ec482b6 --- /dev/null +++ b/open2opaque_flush.go
@@ -0,0 +1,13 @@ +// 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 main + +import ( + log "github.com/golang/glog" +) + +func logFlush() { + log.Flush() +}
diff --git a/regenerate.bash b/regenerate.bash new file mode 100755 index 0000000..6eb19f3 --- /dev/null +++ b/regenerate.bash
@@ -0,0 +1,5 @@ +#!/bin/bash +# 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. +go generate
diff --git a/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto b/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto new file mode 100644 index 0000000..945ddeb --- /dev/null +++ b/testdata/flag_edition_test1_go_proto/flag_edition_test1.proto
@@ -0,0 +1,33 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test1; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = /*unicode comment comment saying Hello, 世界*/ API_OPEN; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map<string, bool> map_field = 10; +}
diff --git a/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto b/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto new file mode 100644 index 0000000..99250db --- /dev/null +++ b/testdata/flag_edition_test2_go_proto/flag_edition_test2.proto
@@ -0,0 +1,34 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test2; + +import "google/protobuf/go_features.proto"; + +message M1 { + option features.(pb.go).api_level = API_OPAQUE; + + message Nested1 { + option + /*multi-line*/ + features.(pb.go) + .api_level = API_HYBRID; + + message Nested2 {} + } + + map<string, bool> map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map<string, bool> map_field = 10; +}
diff --git a/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto b/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto new file mode 100644 index 0000000..6115f77 --- /dev/null +++ b/testdata/flag_edition_test3_go_proto/flag_edition_test3.proto
@@ -0,0 +1,26 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test3; + +import "google/protobuf/go_features.proto"; + +// Leading comment exempts api_level from modifications. +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + // Exemption on message level. + option features.(pb.go).api_level = API_HYBRID; + + message Nested1 { + // Exemption on nested-message level. + option features.(pb.go).api_level = API_OPAQUE; + } +} + +message M2 { + option features.(pb.go).api_level = API_HYBRID; +}
diff --git a/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto b/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto new file mode 100644 index 0000000..912e726 --- /dev/null +++ b/testdata/flag_edition_test4_go_proto/flag_edition_test4.proto
@@ -0,0 +1,23 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test4; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_OPAQUE; + +message M1 { + option features.(pb.go).api_level = API_HYBRID; + + string s = 1; +} + +message M2 { + option features.(pb.go).api_level = API_OPEN; + + string s = 1; +}
diff --git a/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto b/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto new file mode 100644 index 0000000..228d8c7 --- /dev/null +++ b/testdata/flag_edition_test5_go_proto/flag_edition_test5.proto
@@ -0,0 +1,25 @@ +// 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. + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test5; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_HYBRID; + +message M1 { + // Leading comment on message should not be misplaced + // when API flag is inserted. + int32 int_field = 1; +} + +message M2 { + /** + * Leading block comment on message should not be misplaced + * when API flag is inserted. + */ + int32 int_field = 1; +}
diff --git a/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto b/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto new file mode 100644 index 0000000..3c48c6b --- /dev/null +++ b/testdata/flag_edition_test6_go_proto/flag_edition_test6.proto
@@ -0,0 +1,21 @@ +// 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. + +// Something in the first line + +edition = "2023"; + +package net.proto2.go.open2opaque.testdata.flag_edition_test6; + +import "google/protobuf/go_features.proto"; + +option features.(pb.go).api_level = API_OPAQUE; + +message M1 { + message Nested1 { + message SubNested1 { + option features.(pb.go).api_level = API_OPAQUE; + } + } +}
diff --git a/testdata/rewriteme_go_proto/rewriteme.proto b/testdata/rewriteme_go_proto/rewriteme.proto new file mode 100644 index 0000000..5dbc4df --- /dev/null +++ b/testdata/rewriteme_go_proto/rewriteme.proto
@@ -0,0 +1,14 @@ +// 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. + +// Type information for this package is provided by an offline job. Hence +// changes here require the job to run before it can be used in tests. +syntax = "proto2"; + +package rewriteme; + +message M { + optional string s1 = 1; + optional string s2 = 2; +}
diff --git a/testdata/rewriteme_test_want_red_go b/testdata/rewriteme_test_want_red_go new file mode 100644 index 0000000..dd335d2 --- /dev/null +++ b/testdata/rewriteme_test_want_red_go
@@ -0,0 +1,12 @@ +package rewriteme + +import ( + "testing" + + rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto" + "google.golang.org/protobuf/proto" +) + +func Test(t *testing.T) { + _ = rpb.MMaker{S1: proto.String("")}.Make() +}
diff --git a/testdata/rewriteme_want_green_go b/testdata/rewriteme_want_green_go new file mode 100644 index 0000000..e7fee8a --- /dev/null +++ b/testdata/rewriteme_want_green_go
@@ -0,0 +1,20 @@ +// Package rewriteme is used for end to end test of the open2opaque program. +package rewriteme + +import ( + rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto" + + "google.golang.org/protobuf/proto" +) + +func green() *rpb.M { + return rpb.MMaker{S1: proto.String("")}.Make() +} + +func yellow(m *rpb.M) { + m.S1, m.S2 = proto.String(""), proto.String("") +} + +func red(m1, m2 *rpb.M) { + m1.S1 = m2.S1 +}
diff --git a/testdata/rewriteme_want_red_go b/testdata/rewriteme_want_red_go new file mode 100644 index 0000000..445e7ab --- /dev/null +++ b/testdata/rewriteme_want_red_go
@@ -0,0 +1,21 @@ +// Package rewriteme is used for end to end test of the open2opaque program. +package rewriteme + +import ( + rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto" + + "google.golang.org/protobuf/proto" +) + +func green() *rpb.M { + return rpb.MMaker{S1: proto.String("")}.Make() +} + +func yellow(m *rpb.M) { + m.SetS1("") + m.SetS2("") +} + +func red(m1, m2 *rpb.M) { + m1.SetS1(m2.GetS1()) +}
diff --git a/testdata/rewriteme_want_yellow_go b/testdata/rewriteme_want_yellow_go new file mode 100644 index 0000000..3ff20b5 --- /dev/null +++ b/testdata/rewriteme_want_yellow_go
@@ -0,0 +1,21 @@ +// Package rewriteme is used for end to end test of the open2opaque program. +package rewriteme + +import ( + rpb "google.golang.org/open2opaque/internal/fix/testdata/rewriteme_go_proto" + + "google.golang.org/protobuf/proto" +) + +func green() *rpb.M { + return rpb.MMaker{S1: proto.String("")}.Make() +} + +func yellow(m *rpb.M) { + m.SetS1("") + m.SetS2("") +} + +func red(m1, m2 *rpb.M) { + m1.S1 = m2.S1 +}