rpc: check callback params are either exported or builtin types

This commit is contained in:
uwin 2024-10-07 13:44:33 +08:00
parent 65e5ca7d81
commit 9748088a59
3 changed files with 63 additions and 19 deletions

View file

@ -44,10 +44,10 @@ func TestClientRequest(t *testing.T) {
defer client.Close()
var resp echoResult
if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
if err := client.Call(&resp, "test_echo", "hello", 10, &EchoArgs{"world"}); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
if !reflect.DeepEqual(resp, echoResult{"hello", 10, &EchoArgs{"world"}}) {
t.Errorf("incorrect result %#v", resp)
}
}
@ -58,12 +58,12 @@ func TestClientResponseType(t *testing.T) {
client := DialInProc(server)
defer client.Close()
if err := client.Call(nil, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
if err := client.Call(nil, "test_echo", "hello", 10, &EchoArgs{"world"}); err != nil {
t.Errorf("Passing nil as result should be fine, but got an error: %v", err)
}
var resultVar echoResult
// Note: passing the var, not a ref
err := client.Call(resultVar, "test_echo", "hello", 10, &echoArgs{"world"})
err := client.Call(resultVar, "test_echo", "hello", 10, &EchoArgs{"world"})
if err == nil {
t.Error("Passing a var as result should be an error")
}
@ -129,12 +129,12 @@ func TestClientBatchRequest(t *testing.T) {
batch := []BatchElem{
{
Method: "test_echo",
Args: []interface{}{"hello", 10, &echoArgs{"world"}},
Args: []interface{}{"hello", 10, &EchoArgs{"world"}},
Result: new(echoResult),
},
{
Method: "test_echo",
Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
Args: []interface{}{"hello2", 11, &EchoArgs{"world"}},
Result: new(echoResult),
},
{
@ -149,13 +149,13 @@ func TestClientBatchRequest(t *testing.T) {
wantResult := []BatchElem{
{
Method: "test_echo",
Args: []interface{}{"hello", 10, &echoArgs{"world"}},
Result: &echoResult{"hello", 10, &echoArgs{"world"}},
Args: []interface{}{"hello", 10, &EchoArgs{"world"}},
Result: &echoResult{"hello", 10, &EchoArgs{"world"}},
},
{
Method: "test_echo",
Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
Args: []interface{}{"hello2", 11, &EchoArgs{"world"}},
Result: &echoResult{"hello2", 11, &EchoArgs{"world"}},
},
{
Method: "no_such_method",
@ -290,7 +290,7 @@ func TestClientNotify(t *testing.T) {
client := DialInProc(server)
defer client.Close()
if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
if err := client.Notify(context.Background(), "test_echo", "hello", 10, &EchoArgs{"world"}); err != nil {
t.Fatal(err)
}
}
@ -773,7 +773,7 @@ func TestClientHTTP(t *testing.T) {
var (
results = make([]echoResult, 100)
errc = make(chan error, len(results))
wantResult = echoResult{"a", 1, new(echoArgs)}
wantResult = echoResult{"a", 1, new(EchoArgs)}
)
for i := range results {
i := i

View file

@ -24,6 +24,7 @@ import (
"strings"
"sync"
"unicode"
"unicode/utf8"
"github.com/ethereum/go-ethereum/log"
)
@ -136,7 +137,9 @@ func newCallback(receiver, fn reflect.Value) *callback {
fntype := fn.Type()
c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
// Determine parameter types. They must all be exported or builtin types.
c.makeArgTypes()
if err := c.makeArgTypes(); err != nil {
return nil
}
// Verify return types. The function must return at most one error
// and/or one other non-error value.
@ -161,7 +164,7 @@ func newCallback(receiver, fn reflect.Value) *callback {
}
// makeArgTypes composes the argTypes list.
func (c *callback) makeArgTypes() {
func (c *callback) makeArgTypes() error {
fntype := c.fn.Type()
// Skip receiver and context.Context parameter (if present).
firstArg := 0
@ -175,9 +178,15 @@ func (c *callback) makeArgTypes() {
// Add all remaining parameters.
c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg)
for i := firstArg; i < fntype.NumIn(); i++ {
intype := fntype.In(i)
if isBuiltinType(intype) || isExportedType(intype) {
c.argTypes[i-firstArg] = fntype.In(i)
} else {
return fmt.Errorf("param %T is neither a builtin type nor exported type", intype)
}
}
return nil
}
// call invokes the callback.
func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) {
@ -214,6 +223,41 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value
return results[0].Interface(), nil
}
// Is t a builtin type or nested builtin type?
func isBuiltinType(t reflect.Type) bool {
if t.PkgPath() != "" {
return false
}
switch k := t.Kind(); k {
case reflect.Array, reflect.Chan, reflect.Pointer, reflect.Slice, reflect.UnsafePointer:
return isBuiltinType(t.Elem())
case reflect.Map:
return isBuiltinType(t.Key()) && isBuiltinType(t.Elem())
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
if isBuiltinType(t.Field(i).Type) {
return true
}
}
default:
if k >= reflect.Invalid && k <= reflect.UnsafePointer {
return true
}
}
return false
}
// Does t have an exported (upper case) name?
func isExportedType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type (e.g, unnamed type).
rune, _ := utf8.DecodeRuneInString(t.Name())
return t.PkgPath() != "" && unicode.IsUpper(rune)
}
// Does t satisfy the error interface?
func isErrorType(t reflect.Type) bool {
return t.Implements(errorType)

View file

@ -54,14 +54,14 @@ func sequentialIDGenerator() func() ID {
type testService struct{}
type echoArgs struct {
type EchoArgs struct {
S string
}
type echoResult struct {
String string
Int int
Args *echoArgs
Args *EchoArgs
}
type testError struct{}
@ -82,11 +82,11 @@ func (s *testService) Null() any {
return nil
}
func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
func (s *testService) Echo(str string, i int, args *EchoArgs) echoResult {
return echoResult{str, i, args}
}
func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *echoArgs) echoResult {
func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *EchoArgs) echoResult {
return echoResult{str, i, args}
}