Skip to content

Commit 9671c4a

Browse files
authored
cmd/protoc-gen-go-grpc: test the embedded struct at registration time for proper usage (#7438)
1 parent 40f3998 commit 9671c4a

File tree

22 files changed

+412
-77
lines changed

22 files changed

+412
-77
lines changed

balancer/grpclb/grpc_lb_v1/load_balancer_grpc.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

channelz/grpc_channelz_v1/channelz_grpc.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/protoc-gen-go-grpc/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,10 @@ E.g.:
1919

2020
Note that this is not recommended, and the option is only provided to restore
2121
backward compatibility with previously-generated code.
22+
23+
When embedding the `Unimplemented<ServiceName>Server` in a struct that
24+
implements the service, it should be embedded by _value_ instead of as a
25+
_pointer_. If it is embedded as a pointer, it must be assigned to a valid,
26+
non-nil pointer or else unimplemented methods would panic when called. This is
27+
tested at service registration time, and will lead to a panic in
28+
`Register<ServiceName>Server` if it is not embedded properly.

cmd/protoc-gen-go-grpc/go.mod

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,16 @@ module google.golang.org/grpc/cmd/protoc-gen-go-grpc
22

33
go 1.21
44

5-
require google.golang.org/protobuf v1.34.1
5+
require (
6+
google.golang.org/grpc v1.65.0
7+
google.golang.org/protobuf v1.34.1
8+
)
9+
10+
require (
11+
golang.org/x/net v0.26.0 // indirect
12+
golang.org/x/sys v0.21.0 // indirect
13+
golang.org/x/text v0.16.0 // indirect
14+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
15+
)
16+
17+
replace google.golang.org/grpc => ../..

cmd/protoc-gen-go-grpc/go.sum

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
2-
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
3-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
4-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
4+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
5+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
6+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
7+
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
8+
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
9+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
10+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
511
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
612
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=

cmd/protoc-gen-go-grpc/grpc.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ func (serviceGenerateHelper) generateUnimplementedServerType(gen *protogen.Plugi
8484
mustOrShould = "should"
8585
}
8686
// Server Unimplemented struct for forward compatibility.
87-
g.P("// Unimplemented", serverType, " ", mustOrShould, " be embedded to have forward compatible implementations.")
88-
g.P("type Unimplemented", serverType, " struct {")
89-
g.P("}")
87+
g.P("// Unimplemented", serverType, " ", mustOrShould, " be embedded to have")
88+
g.P("// forward compatible implementations.")
89+
g.P("//")
90+
g.P("// NOTE: this should be embedded by value instead of pointer to avoid a nil")
91+
g.P("// pointer dereference when methods are called.")
92+
g.P("type Unimplemented", serverType, " struct {}")
9093
g.P()
9194
for _, method := range service.Methods {
9295
nilArg := ""
@@ -100,6 +103,7 @@ func (serviceGenerateHelper) generateUnimplementedServerType(gen *protogen.Plugi
100103
if *requireUnimplemented {
101104
g.P("func (Unimplemented", serverType, ") mustEmbedUnimplemented", serverType, "() {}")
102105
}
106+
g.P("func (Unimplemented", serverType, ") testEmbeddedByValue() {}")
103107
g.P()
104108
}
105109

@@ -306,6 +310,13 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated
306310
}
307311
serviceDescVar := service.GoName + "_ServiceDesc"
308312
g.P("func Register", service.GoName, "Server(s ", grpcPackage.Ident("ServiceRegistrar"), ", srv ", serverType, ") {")
313+
g.P("// If the following call pancis, it indicates Unimplemented", serverType, " was")
314+
g.P("// embedded by pointer and is nil. This will cause panics if an")
315+
g.P("// unimplemented method is ever invoked, so we test this at initialization")
316+
g.P("// time to prevent it from happening at runtime later due to I/O.")
317+
g.P("if t, ok := srv.(interface { testEmbeddedByValue() }); ok {")
318+
g.P("t.testEmbeddedByValue()")
319+
g.P("}")
309320
g.P("s.RegisterService(&", serviceDescVar, `, srv)`)
310321
g.P("}")
311322
g.P()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package main_test
20+
21+
import (
22+
"testing"
23+
24+
"google.golang.org/grpc"
25+
testgrpc "google.golang.org/grpc/interop/grpc_testing"
26+
)
27+
28+
type unimplEmbeddedByPointer struct {
29+
*testgrpc.UnimplementedTestServiceServer
30+
}
31+
32+
type unimplEmbeddedByValue struct {
33+
testgrpc.UnimplementedTestServiceServer
34+
}
35+
36+
func TestUnimplementedEmbedding(t *testing.T) {
37+
// Embedded by value, this should succeed.
38+
testgrpc.RegisterTestServiceServer(grpc.NewServer(), &unimplEmbeddedByValue{})
39+
defer func() {
40+
if recover() == nil {
41+
t.Fatalf("Expected panic; received none")
42+
}
43+
}()
44+
// Embedded by pointer, this should panic.
45+
testgrpc.RegisterTestServiceServer(grpc.NewServer(), &unimplEmbeddedByPointer{})
46+
}

credentials/alts/internal/proto/grpc_gcp/handshaker_grpc.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/features/proto/echo/echo_grpc.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/helloworld/helloworld/helloworld_grpc.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)