From f5bbded085d21e0f97e2d9478088ffa0ea34d769 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 Dec 2024 19:32:41 +0100 Subject: [PATCH] Initial commit Change-Id: I5f41c4cc0135d6efad2d01aeafb4a6954421c082 --- CONTRIBUTING.md | 33 + LICENSE | 26 + README.md | 6 + gen.go | 8 + genprotos.go | 70 + gentestdata.go | 32 + go.mod | 22 + go.sum | 48 + internal/apiflagdata/go_api_enum.pb.go | 146 + internal/apiflagdata/go_api_enum.proto | 17 + internal/apiflagdata/set/set.go | 50 + internal/apiflagdata/set/set_test.go | 206 + internal/dashboard/stats.pb.go | 2858 +++++++++++++ internal/dashboard/stats.proto | 318 ++ internal/dashboard/stats_protoopaque.pb.go | 2776 +++++++++++++ internal/fix/appendprotos.go | 85 + internal/fix/appendprotos_test.go | 46 + internal/fix/assign.go | 1004 +++++ internal/fix/assign_test.go | 1585 ++++++++ internal/fix/assignswap.go | 149 + internal/fix/assignswap_test.go | 230 ++ internal/fix/build.go | 433 ++ internal/fix/build_test.go | 660 +++ internal/fix/clone.go | 176 + internal/fix/conflictingname.go | 53 + internal/fix/conflictingname_test.go | 139 + internal/fix/converttosetter.go | 374 ++ internal/fix/converttosetter_test.go | 884 ++++ internal/fix/debug.go | 74 + internal/fix/diff.go | 70 + internal/fix/fix.go | 519 +++ internal/fix/fix_test.go | 120 + internal/fix/fixcursor.go | 859 ++++ internal/fix/fiximports.go | 158 + internal/fix/get.go | 421 ++ internal/fix/get_test.go | 329 ++ internal/fix/has.go | 257 ++ internal/fix/has_test.go | 603 +++ internal/fix/hasneeded.go | 271 ++ internal/fix/incdec.go | 51 + internal/fix/incdec_test.go | 103 + internal/fix/naming.go | 61 + internal/fix/naming_test.go | 46 + internal/fix/oneof.go | 497 +++ internal/fix/oneof_test.go | 1484 +++++++ internal/fix/oneofswitch.go | 576 +++ internal/fix/outputparam.go | 232 ++ internal/fix/rules.go | 411 ++ internal/fix/rules_common_test.go | 354 ++ internal/fix/rules_commonload_test.go | 95 + internal/fix/rules_test.go | 1599 ++++++++ internal/fix/stats.go | 1201 ++++++ internal/fix/stats_test.go | 879 ++++ internal/fix/testdata/fake/fake.go | 26 + .../proto2test_go_proto/proto2test.pb.go | 3538 +++++++++++++++++ .../proto2test_go_proto/proto2test.proto | 150 + .../proto3test_go_proto/proto3test.pb.go | 950 +++++ .../proto3test_go_proto/proto3test.proto | 41 + .../proto3test_protoopaque.pb.go | 908 +++++ internal/fix/usepointers.go | 218 + internal/fix/usepointers_test.go | 548 +++ internal/ignore/ignore.go | 80 + internal/ignore/ignore_test.go | 81 + internal/o2o/args/args.go | 23 + internal/o2o/errutil/errutil.go | 37 + internal/o2o/errutil/errutil_test.go | 31 + internal/o2o/fakeloader/fakeloader.go | 240 ++ internal/o2o/loader/loader.go | 98 + internal/o2o/loader/loader_test.go | 204 + internal/o2o/loader/loaderblaze.go | 106 + internal/o2o/loader/loaderconfig.go | 9 + internal/o2o/profile/profile.go | 62 + internal/o2o/rewrite/rewrite.go | 727 ++++ internal/o2o/setapi/setapi.go | 816 ++++ internal/o2o/setapi/setapi_test.go | 1245 ++++++ internal/o2o/statsutil/statsutil.go | 30 + internal/o2o/statsutil/statsutil_test.go | 33 + internal/o2o/syncset/syncset.go | 34 + internal/o2o/syncset/syncset_test.go | 36 + internal/o2o/version/version.go | 86 + internal/o2o/wd/wd.go | 11 + internal/protodetect/protodetect.go | 90 + internal/protodetecttypes/protodetecttypes.go | 65 + internal/protoparse/protoparse.go | 385 ++ internal/protoparse/protoparse_test.go | 315 ++ open2opaque.go | 66 + open2opaque_flush.go | 13 + regenerate.bash | 5 + .../flag_edition_test1.proto | 33 + .../flag_edition_test2.proto | 34 + .../flag_edition_test3.proto | 26 + .../flag_edition_test4.proto | 23 + .../flag_edition_test5.proto | 25 + .../flag_edition_test6.proto | 21 + testdata/rewriteme_go_proto/rewriteme.proto | 14 + testdata/rewriteme_test_want_red_go | 12 + testdata/rewriteme_want_green_go | 20 + testdata/rewriteme_want_red_go | 21 + testdata/rewriteme_want_yellow_go | 21 + 99 files changed, 35261 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gen.go create mode 100644 genprotos.go create mode 100644 gentestdata.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/apiflagdata/go_api_enum.pb.go create mode 100644 internal/apiflagdata/go_api_enum.proto create mode 100644 internal/apiflagdata/set/set.go create mode 100644 internal/apiflagdata/set/set_test.go create mode 100644 internal/dashboard/stats.pb.go create mode 100644 internal/dashboard/stats.proto create mode 100644 internal/dashboard/stats_protoopaque.pb.go create mode 100644 internal/fix/appendprotos.go create mode 100644 internal/fix/appendprotos_test.go create mode 100644 internal/fix/assign.go create mode 100644 internal/fix/assign_test.go create mode 100644 internal/fix/assignswap.go create mode 100644 internal/fix/assignswap_test.go create mode 100644 internal/fix/build.go create mode 100644 internal/fix/build_test.go create mode 100644 internal/fix/clone.go create mode 100644 internal/fix/conflictingname.go create mode 100644 internal/fix/conflictingname_test.go create mode 100644 internal/fix/converttosetter.go create mode 100644 internal/fix/converttosetter_test.go create mode 100644 internal/fix/debug.go create mode 100644 internal/fix/diff.go create mode 100644 internal/fix/fix.go create mode 100644 internal/fix/fix_test.go create mode 100644 internal/fix/fixcursor.go create mode 100644 internal/fix/fiximports.go create mode 100644 internal/fix/get.go create mode 100644 internal/fix/get_test.go create mode 100644 internal/fix/has.go create mode 100644 internal/fix/has_test.go create mode 100644 internal/fix/hasneeded.go create mode 100644 internal/fix/incdec.go create mode 100644 internal/fix/incdec_test.go create mode 100644 internal/fix/naming.go create mode 100644 internal/fix/naming_test.go create mode 100644 internal/fix/oneof.go create mode 100644 internal/fix/oneof_test.go create mode 100644 internal/fix/oneofswitch.go create mode 100644 internal/fix/outputparam.go create mode 100644 internal/fix/rules.go create mode 100644 internal/fix/rules_common_test.go create mode 100644 internal/fix/rules_commonload_test.go create mode 100644 internal/fix/rules_test.go create mode 100644 internal/fix/stats.go create mode 100644 internal/fix/stats_test.go create mode 100644 internal/fix/testdata/fake/fake.go create mode 100644 internal/fix/testdata/proto2test_go_proto/proto2test.pb.go create mode 100644 internal/fix/testdata/proto2test_go_proto/proto2test.proto create mode 100644 internal/fix/testdata/proto3test_go_proto/proto3test.pb.go create mode 100644 internal/fix/testdata/proto3test_go_proto/proto3test.proto create mode 100644 internal/fix/testdata/proto3test_go_proto/proto3test_protoopaque.pb.go create mode 100644 internal/fix/usepointers.go create mode 100644 internal/fix/usepointers_test.go create mode 100644 internal/ignore/ignore.go create mode 100644 internal/ignore/ignore_test.go create mode 100644 internal/o2o/args/args.go create mode 100644 internal/o2o/errutil/errutil.go create mode 100644 internal/o2o/errutil/errutil_test.go create mode 100644 internal/o2o/fakeloader/fakeloader.go create mode 100644 internal/o2o/loader/loader.go create mode 100644 internal/o2o/loader/loader_test.go create mode 100644 internal/o2o/loader/loaderblaze.go create mode 100644 internal/o2o/loader/loaderconfig.go create mode 100644 internal/o2o/profile/profile.go create mode 100644 internal/o2o/rewrite/rewrite.go create mode 100644 internal/o2o/setapi/setapi.go create mode 100644 internal/o2o/setapi/setapi_test.go create mode 100644 internal/o2o/statsutil/statsutil.go create mode 100644 internal/o2o/statsutil/statsutil_test.go create mode 100644 internal/o2o/syncset/syncset.go create mode 100644 internal/o2o/syncset/syncset_test.go create mode 100644 internal/o2o/version/version.go create mode 100644 internal/o2o/wd/wd.go create mode 100644 internal/protodetect/protodetect.go create mode 100644 internal/protodetecttypes/protodetecttypes.go create mode 100644 internal/protoparse/protoparse.go create mode 100644 internal/protoparse/protoparse_test.go create mode 100644 open2opaque.go create mode 100644 open2opaque_flush.go create mode 100755 regenerate.bash create mode 100644 testdata/flag_edition_test1_go_proto/flag_edition_test1.proto create mode 100644 testdata/flag_edition_test2_go_proto/flag_edition_test2.proto create mode 100644 testdata/flag_edition_test3_go_proto/flag_edition_test3.proto create mode 100644 testdata/flag_edition_test4_go_proto/flag_edition_test4.proto create mode 100644 testdata/flag_edition_test5_go_proto/flag_edition_test5.proto create mode 100644 testdata/flag_edition_test6_go_proto/flag_edition_test6.proto create mode 100644 testdata/rewriteme_go_proto/rewriteme.proto create mode 100644 testdata/rewriteme_test_want_red_go create mode 100644 testdata/rewriteme_want_green_go create mode 100644 testdata/rewriteme_want_red_go create mode 100644 testdata/rewriteme_want_yellow_go 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 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 in the + // innermost scope. + // + // 2. or !lastSeen — we couldn’t find the usage of (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("") + } + 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("") + 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("") + } 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= Modified=%t Generated=%t Stats=]", + 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("") + } + 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("") + } + 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: &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: &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 = 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 (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 := "" + 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 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 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 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 "" + } + if len(p.records) == 1 { + return "" + } + 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 [...] + +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=] [] [] [] + +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 map_field = 10; +} + +message M2 { + message Nested1 { + + } + + message Nested2 {} + + map 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 map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map 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 map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map 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 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 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 map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map 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 = "" + } + // 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 map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map 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 map_field = 10; +} + +message M2 { + message Nested1 { + option features.(pb.go).api_level = API_HYBRID; + } + + message Nested2 {} + + map 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 +}