-
Notifications
You must be signed in to change notification settings - Fork 134
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Describe the bug
connect-go internally uses typed nil, which causes issues when performing a nil check in an interceptor.
Since the returned value is a typed nil, a standard if value == nil check does not work as expected, potentially leading to incorrect behavior in interceptors.
To Reproduce
example.proto
syntax = "proto3";
package example.v1;
option go_package = "example/gen/example/v1;examplev1";
message PingRequest {
string message = 1;
}
message PingResponse {
string message = 1;
}
service ExampleService {
rpc Ping(PingRequest) returns (PingResponse) {}
}example_test.go:
package bugreport
import (
"context"
"fmt"
"net"
"net/http"
"testing"
"connectrpc.com/connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
examplev1 "example/gen/example/v1"
"example/gen/example/v1/examplev1connect"
)
type ExampleServer struct{}
func (s *ExampleServer) Ping(
ctx context.Context,
req *connect.Request[examplev1.PingRequest],
) (*connect.Response[examplev1.PingResponse], error) {
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error"))
}
func exampleInterceptor() connect.UnaryInterceptorFunc {
return func(next connect.UnaryFunc) connect.UnaryFunc {
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
res, err := next(ctx, req)
if res != nil {
fmt.Println("res is not nil")
res.Header().Add("example", "example") // <- this operation is panic (res is nil)
}
return res, err
}
}
}
type serverAndClient struct {
server *http.Server
client examplev1connect.ExampleServiceClient
}
func setupServerAndClient(
t *testing.T,
) *serverAndClient {
t.Helper()
es := &ExampleServer{}
mux := http.NewServeMux()
mux.Handle(examplev1connect.NewExampleServiceHandler(es, connect.WithInterceptors(exampleInterceptor())))
li, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
_, port, err := net.SplitHostPort(li.Addr().String())
if err != nil {
t.Fatal(err)
}
srv := &http.Server{
// Use h2c so we can serve HTTP/2 without TLS.
Handler: h2c.NewHandler(mux, &http2.Server{}),
}
go srv.Serve(li)
client := examplev1connect.NewExampleServiceClient(
http.DefaultClient,
fmt.Sprintf("http://localhost:%s", port),
)
return &serverAndClient{
server: srv,
client: client,
}
}
func (r *serverAndClient) close(t *testing.T) {
t.Helper()
err := r.server.Close()
if err != nil {
t.Fatal(err)
}
}
func TestThatReproducesBug(t *testing.T) {
sc := setupServerAndClient(t)
t.Cleanup(func() {
sc.close(t)
})
c := sc.client
ctx := t.Context()
c.Ping(ctx, &connect.Request[examplev1.PingRequest]{Msg: &examplev1.PingRequest{}})
}test result
res is not nil
2025/02/13 18:43:27 http: panic serving 127.0.0.1:62326: runtime error: invalid memory address or nil pointer dereference
goroutine 13 [running]:
net/http.(*conn).serve.func1()
/Users/replu/sdk/go1.24.0/src/net/http/server.go:1947 +0xb0
panic({0x101058020?, 0x1013f5400?})
/Users/replu/sdk/go1.24.0/src/runtime/panic.go:787 +0x124
connectrpc.com/connect.(*Response[...]).Header(...)
/Users/replu/dev/pkg/mod/connectrpc.com/[email protected]/connect.go:265
example.setupServerAndClient.exampleInterceptor.func1.1({0x1010f5150?, 0x140001b6000?}, {0x1010f7230?, 0x140001ba080?})
/Users/replu/dev/src/tmp/example_test.go:33 +0x80
connectrpc.com/connect.NewUnaryHandler[...].func2({0x1017a81e0, 0x1400018a080})
/Users/replu/dev/pkg/mod/connectrpc.com/[email protected]/handler.go:69 +0x80
connectrpc.com/connect.(*Handler).ServeHTTP(0x14000160150, {0x1010f4a88, 0x140001b8000}, 0x14000162280)
/Users/replu/dev/pkg/mod/connectrpc.com/[email protected]/handler.go:237 +0x7e8
example/gen/example/v1/examplev1connect.NewExampleServiceHandler.func1({0x1010f4a88, 0x140001b8000}, 0x14000162280)
/Users/replu/dev/src/tmp/gen/example/v1/examplev1connect/example.connect.go:100 +0x90
net/http.HandlerFunc.ServeHTTP(0x1400013a0c0?, {0x1010f4a88?, 0x140001b8000?}, 0x0?)
/Users/replu/sdk/go1.24.0/src/net/http/server.go:2294 +0x38
net/http.(*ServeMux).ServeHTTP(0x14000182150?, {0x1010f4a88, 0x140001b8000}, 0x14000162280)
/Users/replu/sdk/go1.24.0/src/net/http/server.go:2822 +0x1b4
golang.org/x/net/http2/h2c.h2cHandler.ServeHTTP({{0x1010f0c40?, 0x1400013a0c0?}, 0x140001601c0?}, {0x1010f4a88, 0x140001b8000}, 0x14000162280)
/Users/replu/dev/pkg/mod/golang.org/x/[email protected]/http2/h2c/h2c.go:125 +0x4a8
net/http.serverHandler.ServeHTTP({0x14000200870?}, {0x1010f4a88?, 0x140001b8000?}, 0x1?)
/Users/replu/sdk/go1.24.0/src/net/http/server.go:3301 +0xbc
net/http.(*conn).serve(0x14000172870, {0x1010f5118, 0x14000182120})
/Users/replu/sdk/go1.24.0/src/net/http/server.go:2102 +0x52c
created by net/http.(*Server).Serve in goroutine 5
/Users/replu/sdk/go1.24.0/src/net/http/server.go:3454 +0x3d8
PASS
ok example 0.541sEnvironment (please complete the following information):
connect-goversion or commit:v1.18.1go version:go version go1.24.0 darwin/arm64- your complete
go.mod:
go.mod:
module example
go 1.24.0
require (
connectrpc.com/connect v1.18.1
golang.org/x/net v0.34.0
)
require (
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20241031151143-70f632351282.1 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.3-20241127180247-a33202765966.1 // indirect
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250106231242-56271afbd6ce.1 // indirect
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.3-20250106231242-56271afbd6ce.1 // indirect
buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.3-20241007202033-cf42259fcbfc.1 // indirect
buf.build/go/bufplugin v0.6.0 // indirect
buf.build/go/protoyaml v0.3.1 // indirect
buf.build/go/spdx v0.2.0 // indirect
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect
connectrpc.com/otelconnect v0.7.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/bufbuild/buf v1.50.0 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/bufbuild/protoplugin v0.0.0-20250106231243-3a819552c9d9 // indirect
github.com/bufbuild/protovalidate-go v0.8.2 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/containerd v1.7.25 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.5.0+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fullstorydev/grpcurl v1.9.2 // indirect
github.com/go-chi/chi/v5 v5.2.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/cel-go v0.22.1 // indirect
github.com/google/go-containerregistry v0.20.2 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect
github.com/jhump/protoreflect v1.16.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/mount v0.3.4 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/reexec v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.48.2 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/encoding v0.4.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/vbatts/tar-split v0.11.6 // indirect
go.lsp.dev/jsonrpc2 v0.10.0 // indirect
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
go.lsp.dev/protocol v0.12.0 // indirect
go.lsp.dev/uri v0.3.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/grpc v1.69.4 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect
)
tool (
connectrpc.com/connect/cmd/protoc-gen-connect-go
github.com/bufbuild/buf/cmd/buf
github.com/fullstorydev/grpcurl/cmd/grpcurl
google.golang.org/protobuf/cmd/protoc-gen-go
)Additional context
I have identified one occurrence of this problem in https://github.com/connectrpc/connect-go/blob/main/handler.go#L51-L57
If this were the only, I could send a PR. However, I have not investigated whether there are other occurrences in the codebase.
tomtwinkle, ydah, daisuke0131 and herlon214
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working