From 3a0415fb190731b6722ce8188533f8573451fa8e Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Mon, 8 Apr 2024 15:47:04 +1000 Subject: [PATCH] fix: handle nil pointers in deepcopy (#1195) Previously a deepcopy involving nil pointers would end up with a pointer to a zero value. Needed as part of https://github.com/TBD54566975/ftl/pull/1155 --- .golangci.yml | 2 + backend/schema/builtin.go | 2 +- backend/schema/data.go | 2 +- backend/schema/normalise.go | 2 +- backend/schema/validate.go | 4 +- buildengine/project.go | 2 +- buildengine/testdata/projects/alpha/go.mod | 1 - buildengine/testdata/projects/alpha/go.sum | 2 - buildengine/testdata/projects/another/go.mod | 1 - buildengine/testdata/projects/another/go.sum | 2 - buildengine/testdata/projects/other/go.mod | 1 - buildengine/testdata/projects/other/go.sum | 2 - examples/go/echo/go.mod | 1 - examples/go/echo/go.sum | 2 - go-runtime/compile/build.go | 2 +- go-runtime/compile/testdata/failing/go.mod | 1 - go-runtime/compile/testdata/failing/go.sum | 2 - go-runtime/compile/testdata/one/go.mod | 1 - go-runtime/compile/testdata/one/go.sum | 2 - go-runtime/compile/testdata/two/go.mod | 1 - go-runtime/compile/testdata/two/go.sum | 2 - go.mod | 1 - go.sum | 2 - internal/reflect/reflect.go | 311 +++++++++++++++++++ internal/reflect/reflect_test.go | 112 +++++++ 25 files changed, 432 insertions(+), 31 deletions(-) create mode 100644 internal/reflect/reflect.go create mode 100644 internal/reflect/reflect_test.go diff --git a/.golangci.yml b/.golangci.yml index 7b9a9ec6f2..460fd93e03 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -104,6 +104,8 @@ linters-settings: desc: "use fmt.Errorf or errors.New" - pkg: os/exec desc: "use github.com/TBD54566975/ftl/internal/exec" + - pkg: golang.design/x/reflect + desc: "use github.com/TBD54566975/ftl/internal/reflect" # wrapcheck: # ignorePackageGlobs: # - github.com/TBD54566975/ftl/* diff --git a/backend/schema/builtin.go b/backend/schema/builtin.go index 803170ba53..ab2f0a9e7b 100644 --- a/backend/schema/builtin.go +++ b/backend/schema/builtin.go @@ -1,6 +1,6 @@ package schema -import "golang.design/x/reflect" +import "github.com/TBD54566975/ftl/internal/reflect" // BuiltinsSource is the schema source code for built-in types. const BuiltinsSource = ` diff --git a/backend/schema/data.go b/backend/schema/data.go index a447c860b9..2b630125dc 100644 --- a/backend/schema/data.go +++ b/backend/schema/data.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "golang.design/x/reflect" + "github.com/TBD54566975/ftl/internal/reflect" "google.golang.org/protobuf/proto" schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema" diff --git a/backend/schema/normalise.go b/backend/schema/normalise.go index fdd97c0266..ac2a4285ab 100644 --- a/backend/schema/normalise.go +++ b/backend/schema/normalise.go @@ -1,6 +1,6 @@ package schema -import "golang.design/x/reflect" +import "github.com/TBD54566975/ftl/internal/reflect" // Normalise clones and normalises (zeroes) positional information in schema Nodes. func Normalise[T Node](n T) T { diff --git a/backend/schema/validate.go b/backend/schema/validate.go index 335da07101..e6038e8644 100644 --- a/backend/schema/validate.go +++ b/backend/schema/validate.go @@ -9,9 +9,9 @@ import ( "sort" "strings" + dc "github.com/TBD54566975/ftl/internal/reflect" "github.com/alecthomas/participle/v2" "github.com/alecthomas/types/optional" - xreflect "golang.design/x/reflect" "golang.org/x/exp/maps" "github.com/TBD54566975/ftl/internal/errors" @@ -47,7 +47,7 @@ func MustValidate(schema *Schema) *Schema { // Validate clones, normalises and semantically valies a schema. func Validate(schema *Schema) (*Schema, error) { - schema = xreflect.DeepCopy(schema) + schema = dc.DeepCopy(schema) modules := map[string]bool{} merr := []error{} ingress := map[string]*Verb{} diff --git a/buildengine/project.go b/buildengine/project.go index dfa5257b9f..37a15b634a 100644 --- a/buildengine/project.go +++ b/buildengine/project.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "golang.design/x/reflect" + "github.com/TBD54566975/ftl/internal/reflect" "github.com/TBD54566975/ftl/common/moduleconfig" ) diff --git a/buildengine/testdata/projects/alpha/go.mod b/buildengine/testdata/projects/alpha/go.mod index defad22564..64cf7cf91d 100644 --- a/buildengine/testdata/projects/alpha/go.mod +++ b/buildengine/testdata/projects/alpha/go.mod @@ -32,7 +32,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/buildengine/testdata/projects/alpha/go.sum b/buildengine/testdata/projects/alpha/go.sum index 25fc10338d..fdea3963d3 100644 --- a/buildengine/testdata/projects/alpha/go.sum +++ b/buildengine/testdata/projects/alpha/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/buildengine/testdata/projects/another/go.mod b/buildengine/testdata/projects/another/go.mod index cc1244ead9..cea2d677f1 100644 --- a/buildengine/testdata/projects/another/go.mod +++ b/buildengine/testdata/projects/another/go.mod @@ -32,7 +32,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/buildengine/testdata/projects/another/go.sum b/buildengine/testdata/projects/another/go.sum index 25fc10338d..fdea3963d3 100644 --- a/buildengine/testdata/projects/another/go.sum +++ b/buildengine/testdata/projects/another/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/buildengine/testdata/projects/other/go.mod b/buildengine/testdata/projects/other/go.mod index 708c86aa4a..c95ae77de0 100644 --- a/buildengine/testdata/projects/other/go.mod +++ b/buildengine/testdata/projects/other/go.mod @@ -32,7 +32,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/buildengine/testdata/projects/other/go.sum b/buildengine/testdata/projects/other/go.sum index 25fc10338d..fdea3963d3 100644 --- a/buildengine/testdata/projects/other/go.sum +++ b/buildengine/testdata/projects/other/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/examples/go/echo/go.mod b/examples/go/echo/go.mod index 2785d88fdb..dbc28fb2dc 100644 --- a/examples/go/echo/go.mod +++ b/examples/go/echo/go.mod @@ -34,7 +34,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/examples/go/echo/go.sum b/examples/go/echo/go.sum index 25fc10338d..fdea3963d3 100644 --- a/examples/go/echo/go.sum +++ b/examples/go/echo/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index 8d3df46ff1..1d49e09b89 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" + "github.com/TBD54566975/ftl/internal/reflect" "github.com/TBD54566975/scaffolder" - "golang.design/x/reflect" "golang.org/x/mod/modfile" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/proto" diff --git a/go-runtime/compile/testdata/failing/go.mod b/go-runtime/compile/testdata/failing/go.mod index 3c5cf0bde4..20b79856c6 100644 --- a/go-runtime/compile/testdata/failing/go.mod +++ b/go-runtime/compile/testdata/failing/go.mod @@ -34,7 +34,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/go-runtime/compile/testdata/failing/go.sum b/go-runtime/compile/testdata/failing/go.sum index 25fc10338d..fdea3963d3 100644 --- a/go-runtime/compile/testdata/failing/go.sum +++ b/go-runtime/compile/testdata/failing/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/go-runtime/compile/testdata/one/go.mod b/go-runtime/compile/testdata/one/go.mod index e22c2d1896..02838ec1ed 100644 --- a/go-runtime/compile/testdata/one/go.mod +++ b/go-runtime/compile/testdata/one/go.mod @@ -34,7 +34,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/go-runtime/compile/testdata/one/go.sum b/go-runtime/compile/testdata/one/go.sum index 25fc10338d..fdea3963d3 100644 --- a/go-runtime/compile/testdata/one/go.sum +++ b/go-runtime/compile/testdata/one/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/go-runtime/compile/testdata/two/go.mod b/go-runtime/compile/testdata/two/go.mod index 8fca12cf02..e78011106d 100644 --- a/go-runtime/compile/testdata/two/go.mod +++ b/go-runtime/compile/testdata/two/go.mod @@ -34,7 +34,6 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/go-runtime/compile/testdata/two/go.sum b/go-runtime/compile/testdata/two/go.sum index 25fc10338d..fdea3963d3 100644 --- a/go-runtime/compile/testdata/two/go.sum +++ b/go-runtime/compile/testdata/two/go.sum @@ -103,8 +103,6 @@ go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9os go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= diff --git a/go.mod b/go.mod index 9901eaee34..ae33de1256 100644 --- a/go.mod +++ b/go.mod @@ -104,7 +104,6 @@ require ( github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b github.com/swaggest/refl v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b golang.org/x/crypto v0.21.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 4e6e5eb47b..4ec1dabb3f 100644 --- a/go.sum +++ b/go.sum @@ -241,8 +241,6 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b h1:lkOPTy76R9NZ6FeDQWkDj3NsLtD8Csc9AAFYEl3kiME= -golang.design/x/reflect v0.0.0-20220504060917-02c43be63f3b/go.mod h1:QXG482h3unP32W/YwIPOc+09bvY447B7T+iLjC/JPcA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= diff --git a/internal/reflect/reflect.go b/internal/reflect/reflect.go new file mode 100644 index 0000000000..2174e256dd --- /dev/null +++ b/internal/reflect/reflect.go @@ -0,0 +1,311 @@ +// From: https://github.com/golang-design/reflect/blob/main/deepcopy.go + +// Copyright 2022 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +// Package reflect implements the proposal https://go.dev/issue/51520. +// +// Warning: Not largely tested. Use it with care. +// nolint +package reflect + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// DeepCopyOption represents an option to customize deep copied results. +type DeepCopyOption func(opt *copyConfig) + +type copyConfig struct { + disallowCopyUnexported bool + disallowCopyCircular bool + disallowCopyBidirectionalChan bool + disallowCopyTypes []reflect.Type +} + +// DisallowCopyUnexported returns a DeepCopyOption that disables the behavior +// of copying unexported fields. +func DisallowCopyUnexported() DeepCopyOption { + return func(opt *copyConfig) { + opt.disallowCopyUnexported = true + } +} + +// DisallowCopyCircular returns a DeepCopyOption that disables the behavior +// of copying circular structures. +func DisallowCopyCircular() DeepCopyOption { + return func(opt *copyConfig) { + opt.disallowCopyCircular = true + } +} + +// DisallowCopyBidirectionalChan returns a DeepCopyOption that disables +// the behavior of producing new channel when a bidirectional channel is copied. +func DisallowCopyBidirectionalChan() DeepCopyOption { + return func(opt *copyConfig) { + opt.disallowCopyBidirectionalChan = true + } +} + +// DisallowTypes returns a DeepCopyOption that disallows copying any types +// that are in given values. +func DisallowTypes(val ...any) DeepCopyOption { + return func(opt *copyConfig) { + for i := range val { + opt.disallowCopyTypes = append(opt.disallowCopyTypes, reflect.TypeOf(val[i])) + } + } +} + +// DeepCopy copies src to dst recursively. +// +// Two values of identical type are deeply copied if one of the following +// cases apply. +// +// Numbers, bools, strings are deeply copied and have different underlying +// memory address. +// +// Slice and Array values are deeply copied, including its elements. +// +// Map values are deeply copied for all of its key and corresponding +// values. +// +// Pointer values are deeply copied for their pointed value, and the +// pointer points to the deeply copied value. +// +// Struct values are deeply copied for all fields, including exported +// and unexported. +// +// Interface values are deeply copied if the underlying type can be +// deeply copied. +// +// There are a few exceptions that may result in a deeply copied value not +// deeply equal (asserted by DeepEqual(dst, src)) to the source value: +// +// 1. Func values are still refer to the same function +// 2. Chan values are replaced by newly created channels +// 3. One-way Chan values (receive or read-only) values are still refer +// to the same channel +// +// Note that while correct uses of DeepCopy do exist, they are not rare. +// The use of DeepCopy often indicates the copying object does not contain +// a singleton or is never meant to be copied, such as sync.Mutex, os.File, +// net.Conn, js.Value, etc. In these cases, the copied value retains the +// memory representations of the source value but may result in unexpected +// consequences in follow-up usage, the caller should clear these values +// depending on their usage context. +// +// To change these predefined behaviors, use provided DeepCopyOption. +func DeepCopy[T any](src T, opts ...DeepCopyOption) (dst T) { + ptrs := map[uintptr]any{} + conf := ©Config{} + for _, opt := range opts { + opt(conf) + } + + ret := copyAny(src, ptrs, conf) + if v, ok := ret.(T); ok { + dst = v + return + } + panic(fmt.Sprintf("reflect: internal error: copied value is not typed in %T, got %T", src, ret)) +} + +func copyAny(src any, ptrs map[uintptr]any, copyConf *copyConfig) (dst any) { + + if len(copyConf.disallowCopyTypes) != 0 { + for i := range copyConf.disallowCopyTypes { + if reflect.TypeOf(src) == copyConf.disallowCopyTypes[i] { + panic(fmt.Sprintf("reflect: deep copying type %T is disallowed", src)) + } + } + } + + v := reflect.ValueOf(src) + if !v.IsValid() { + return src + } + + // Look up the corresponding copy function. + switch v.Kind() { + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, + reflect.Complex64, reflect.Complex128, reflect.Func: + dst = copyPremitive(src, ptrs, copyConf) + case reflect.String: + dst = strings.Clone(src.(string)) + case reflect.Slice: + dst = copySlice(src, ptrs, copyConf) + case reflect.Array: + dst = copyArray(src, ptrs, copyConf) + case reflect.Map: + dst = copyMap(src, ptrs, copyConf) + case reflect.Ptr, reflect.UnsafePointer: + dst = copyPointer(src, ptrs, copyConf) + case reflect.Struct: + dst = copyStruct(src, ptrs, copyConf) + case reflect.Interface: + dst = copyAny(src, ptrs, copyConf) + case reflect.Chan: + dst = copyChan(src, ptrs, copyConf) + default: + panic(fmt.Sprintf("reflect: internal error: unknown type %v", v.Kind())) + } + return +} + +func copyPremitive(src any, ptr map[uintptr]any, copyConf *copyConfig) (dst any) { + kind := reflect.ValueOf(src).Kind() + switch kind { + case reflect.Array, reflect.Chan, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer: + panic(fmt.Sprintf("reflect: internal error: type %v is not a primitive", kind)) + } + dst = src + return +} + +func copySlice(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + kind := v.Kind() + if kind != reflect.Slice { + panic(fmt.Sprintf("reflect: internal error: type %v is not a slice", kind)) + } + + size := v.Len() + t := reflect.TypeOf(x) + dc := reflect.MakeSlice(t, size, size) + for i := 0; i < size; i++ { + iv := reflect.ValueOf(copyAny(v.Index(i).Interface(), ptrs, copyConf)) + if iv.IsValid() { + dc.Index(i).Set(iv) + } + } + return dc.Interface() +} + +func copyArray(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + if v.Kind() != reflect.Array { + panic(fmt.Errorf("reflect: internal error: must be an Array; got %v", v.Kind())) + } + t := reflect.TypeOf(x) + size := t.Len() + dc := reflect.New(reflect.ArrayOf(size, t.Elem())).Elem() + for i := 0; i < size; i++ { + item := copyAny(v.Index(i).Interface(), ptrs, copyConf) + dc.Index(i).Set(reflect.ValueOf(item)) + } + return dc.Interface() +} + +func copyMap(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + if v.Kind() != reflect.Map { + panic(fmt.Errorf("reflect: internal error: must be a Map; got %v", v.Kind())) + } + t := reflect.TypeOf(x) + dc := reflect.MakeMapWithSize(t, v.Len()) + iter := v.MapRange() + for iter.Next() { + item := copyAny(iter.Value().Interface(), ptrs, copyConf) + k := copyAny(iter.Key().Interface(), ptrs, copyConf) + dc.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(item)) + } + return dc.Interface() +} + +func copyPointer(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + t := reflect.TypeOf(x) + + if v.Kind() != reflect.Pointer { + panic(fmt.Errorf("reflect: internal error: must be a Pointer or Ptr; got %v", v.Kind())) + } + + if v.IsNil() { + return reflect.New(t).Elem().Interface() + } + + addr := uintptr(v.UnsafePointer()) + if dc, ok := ptrs[addr]; ok { + if copyConf.disallowCopyCircular { + panic("reflect: deep copy dircular value is disallowed") + } + return dc + } + + dc := reflect.New(t.Elem()) + ptrs[addr] = dc.Interface() + + item := copyAny(v.Elem().Interface(), ptrs, copyConf) + iv := reflect.ValueOf(item) + if iv.IsValid() { + dc.Elem().Set(reflect.ValueOf(item)) + } + return dc.Interface() +} + +func copyStruct(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + if v.Kind() != reflect.Struct { + panic(fmt.Errorf("reflect: internal error: must be a Struct; got %v", v.Kind())) + } + t := reflect.TypeOf(x) + dc := reflect.New(t) + for i := 0; i < t.NumField(); i++ { + if copyConf.disallowCopyUnexported { + f := t.Field(i) + if f.PkgPath != "" { + continue + } + item := copyAny(v.Field(i).Interface(), ptrs, copyConf) + dc.Elem().Field(i).Set(reflect.ValueOf(item)) + } else { + item := copyAny(valueInterfaceUnsafe(v.Field(i)), ptrs, copyConf) + setField(dc.Elem().Field(i), reflect.ValueOf(item)) + } + } + return dc.Elem().Interface() +} + +func copyChan(x any, ptrs map[uintptr]any, copyConf *copyConfig) any { + v := reflect.ValueOf(x) + if v.Kind() != reflect.Chan { + panic(fmt.Errorf("reflect: internal error: must be a Chan; got %v", v.Kind())) + } + t := reflect.TypeOf(x) + dir := t.ChanDir() + var dc any + switch dir { + case reflect.BothDir: + if !copyConf.disallowCopyBidirectionalChan { + dc = reflect.MakeChan(t, v.Cap()).Interface() + } + fallthrough + case reflect.SendDir, reflect.RecvDir: + dc = x + } + return dc +} + +// valueInterfaceUnsafe overpasses the reflect package check regarding +// unexported methods. +func valueInterfaceUnsafe(v reflect.Value) any { + return reflect_valueInterface(v, false) +} + +// setField sets the given value to the field value, regardless whether +// the filed is exported or not. +func setField(field reflect.Value, value reflect.Value) { + reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(value) +} + +//go:linkname reflect_valueInterface reflect.valueInterface +func reflect_valueInterface(v reflect.Value, safe bool) any diff --git a/internal/reflect/reflect_test.go b/internal/reflect/reflect_test.go new file mode 100644 index 0000000000..a6f138e042 --- /dev/null +++ b/internal/reflect/reflect_test.go @@ -0,0 +1,112 @@ +package reflect + +import ( + "testing" +) + +type structOfPointers struct { + intPtr *int + floatPtr *float64 + structPtr *structOfPointers +} + +func TestCopyStructOfPointers(t *testing.T) { + one := 1 + two := 2 + half := 0.5 + quarter := 0.25 + + for _, tt := range []struct { + name string + input structOfPointers + }{ + { + "StructWithinStruct", + structOfPointers{ + intPtr: &one, + floatPtr: &half, + structPtr: &structOfPointers{ + intPtr: &two, + floatPtr: &quarter, + structPtr: nil, + }, + }, + }, + { + "StructWithinStructWithinStruct", + structOfPointers{ + intPtr: &one, + floatPtr: &half, + structPtr: &structOfPointers{ + intPtr: &two, + floatPtr: &quarter, + structPtr: &structOfPointers{ + intPtr: &two, + floatPtr: &quarter, + structPtr: nil, + }, + }, + }, + }, + { + "StructWithNils", + structOfPointers{ + intPtr: nil, + floatPtr: nil, + structPtr: nil, + }, + }, + { + "StructWithStructWithNils", + structOfPointers{ + intPtr: &one, + floatPtr: &half, + structPtr: &structOfPointers{ + intPtr: nil, + floatPtr: nil, + structPtr: nil, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + output := DeepCopy(tt.input) + testEqualityOfStruct(t, &tt.input, &output) + // #nosec G601 + }) + } +} + +func testEqualityOfStruct(t *testing.T, expected, actual *structOfPointers) { + t.Helper() + if expected == nil || actual == nil { + if expected != actual { + t.Errorf("struct point does not match nil struct pointer. expected %v, got %v", expected, actual) + } + return + } + + testEqualityOfInt(t, expected.intPtr, actual.intPtr) + testEqualityOfFloat64(t, expected.floatPtr, actual.floatPtr) + testEqualityOfStruct(t, expected.structPtr, actual.structPtr) +} + +func testEqualityOfInt(t *testing.T, expected, actual *int) { + t.Helper() + if expected == nil || actual == nil { + if expected != actual { + t.Errorf("int point does not match nil int pointer. expected %v, got %v", expected, actual) + } + return + } +} + +func testEqualityOfFloat64(t *testing.T, expected, actual *float64) { + t.Helper() + if expected == nil || actual == nil { + if expected != actual { + t.Errorf("float point does not match nil float pointer. expected %v, got %v", expected, actual) + } + return + } +}