| // Copyright 2015 The Vanadium 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 rpc |
| |
| import ( |
| "reflect" |
| "sort" |
| "sync" |
| |
| "v.io/v23/context" |
| "v.io/v23/glob" |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/signature" |
| "v.io/v23/verror" |
| ) |
| |
| // Describer may be implemented by an underlying object served by the |
| // ReflectInvoker, in order to describe the interfaces that the object |
| // implements. This describes all data in signature.Interface that the |
| // ReflectInvoker cannot obtain through reflection; basically everything except |
| // the method names and types. |
| // |
| // Note that a single object may implement multiple interfaces; to describe such |
| // an object, simply return more than one elem in the returned list. |
| type Describer interface { |
| // Describe the underlying object. The implementation must be idempotent |
| // across different instances of the same underlying type; the ReflectInvoker |
| // calls this once per type and caches the results. |
| Describe__() []InterfaceDesc |
| } |
| |
| // InterfaceDesc describes an interface; it is similar to signature.Interface, |
| // without the information that can be obtained via reflection. |
| type InterfaceDesc struct { |
| Name string |
| PkgPath string |
| Doc string |
| Embeds []EmbedDesc |
| Methods []MethodDesc |
| } |
| |
| // EmbedDesc describes an embedded interface; it is similar to signature.Embed, |
| // without the information that can be obtained via reflection. |
| type EmbedDesc struct { |
| Name string |
| PkgPath string |
| Doc string |
| } |
| |
| // MethodDesc describes an interface method; it is similar to signature.Method, |
| // without the information that can be obtained via reflection. |
| type MethodDesc struct { |
| Name string |
| Doc string |
| InArgs []ArgDesc // Input arguments |
| OutArgs []ArgDesc // Output arguments |
| InStream ArgDesc // Input stream (client to server) |
| OutStream ArgDesc // Output stream (server to client) |
| Tags []*vdl.Value // Method tags |
| } |
| |
| // ArgDesc describes an argument; it is similar to signature.Arg, without the |
| // information that can be obtained via reflection. |
| type ArgDesc struct { |
| Name string |
| Doc string |
| } |
| |
| type reflectInvoker struct { |
| rcvr reflect.Value |
| methods map[string]methodInfo // used by Prepare and Invoke |
| sig []signature.Interface // used by Signature and MethodSignature |
| } |
| |
| var _ Invoker = (*reflectInvoker)(nil) |
| |
| // methodInfo holds the runtime information necessary for Prepare and Invoke. |
| type methodInfo struct { |
| rvFunc reflect.Value // Function representing the method. |
| rtInArgs []reflect.Type // In arg types, not including receiver and call. |
| |
| // rtStreamCall holds the type of the typesafe streaming call, if any. |
| // rvStreamCallInit is the associated Init function. |
| rtStreamCall reflect.Type |
| rvStreamCallInit reflect.Value |
| |
| tags []*vdl.Value // Tags from the signature. |
| } |
| |
| // ReflectInvoker returns an Invoker implementation that uses reflection to make |
| // each compatible exported method in obj available. E.g.: |
| // |
| // type impl struct{} |
| // func (impl) NonStreaming(ctx *context.T, call rpc.ServerCall, ...) (...) |
| // func (impl) Streaming(ctx *context.T, call *MyCall, ...) (...) |
| // |
| // The first in-arg must be context.T. The second in-arg must be a call; for |
| // non-streaming methods it must be rpc.ServerCall, and for streaming methods it |
| // must be a pointer to a struct that implements rpc.StreamServerCall, and also |
| // adds typesafe streaming wrappers. Here's an example that streams int32 from |
| // client to server, and string from server to client: |
| // |
| // type MyCall struct { rpc.StreamServerCall } |
| // |
| // // Init initializes MyCall via rpc.StreamServerCall. |
| // func (*MyCall) Init(rpc.StreamServerCall) {...} |
| // |
| // // RecvStream returns the receiver side of the server stream. |
| // func (*MyCall) RecvStream() interface { |
| // Advance() bool |
| // Value() int32 |
| // Err() error |
| // } {...} |
| // |
| // // SendStream returns the sender side of the server stream. |
| // func (*MyCall) SendStream() interface { |
| // Send(item string) error |
| // } {...} |
| // |
| // We require the streaming call arg to have this structure so that we can |
| // capture the streaming in and out arg types via reflection. We require it to |
| // be a concrete type with an Init func so that we can create new instances, |
| // also via reflection. |
| // |
| // As a temporary special-case, we also allow generic streaming methods: |
| // |
| // func (impl) Generic(ctx *context.T, call rpc.StreamServerCall, ...) (...) |
| // |
| // The problem with allowing this form is that via reflection we can no longer |
| // determine whether the server performs streaming, or what the streaming in and |
| // out types are. |
| // TODO(toddw): Remove this special-case. |
| // |
| // The ReflectInvoker silently ignores unexported methods, and exported methods |
| // whose first argument doesn't implement rpc.ServerCall. All other methods |
| // must follow the above rules; bad method types cause an error to be returned. |
| // |
| // If obj implements the Describer interface, we'll use it to describe portions |
| // of the object signature that cannot be retrieved via reflection; |
| // e.g. method tags, documentation, variable names, etc. |
| func ReflectInvoker(obj interface{}) (Invoker, error) { |
| rt := reflect.TypeOf(obj) |
| info := reflectCache.lookup(rt) |
| if info == nil { |
| // Concurrent calls may cause reflectCache.set to be called multiple times. |
| // This race is benign; the info for a given type never changes. |
| var err error |
| if info, err = newReflectInfo(obj); err != nil { |
| return nil, err |
| } |
| reflectCache.set(rt, info) |
| } |
| return reflectInvoker{reflect.ValueOf(obj), info.methods, info.sig}, nil |
| } |
| |
| // ReflectInvokerOrDie is the same as ReflectInvoker, but panics on all errors. |
| func ReflectInvokerOrDie(obj interface{}) Invoker { |
| invoker, err := ReflectInvoker(obj) |
| if err != nil { |
| panic(err) |
| } |
| return invoker |
| } |
| |
| // Prepare implements the Invoker.Prepare method. |
| func (ri reflectInvoker) Prepare(ctx *context.T, method string, _ int) ([]interface{}, []*vdl.Value, error) { |
| info, ok := ri.methods[method] |
| if !ok { |
| return nil, nil, verror.New(verror.ErrUnknownMethod, nil, method) |
| } |
| // Return the tags and new in-arg objects. |
| var argptrs []interface{} |
| if len(info.rtInArgs) > 0 { |
| argptrs = make([]interface{}, len(info.rtInArgs)) |
| for ix, rtInArg := range info.rtInArgs { |
| argptrs[ix] = reflect.New(rtInArg).Interface() |
| } |
| } |
| |
| return argptrs, info.tags, nil |
| } |
| |
| // Invoke implements the Invoker.Invoke method. |
| func (ri reflectInvoker) Invoke(ctx *context.T, call StreamServerCall, method string, argptrs []interface{}) ([]interface{}, error) { |
| info, ok := ri.methods[method] |
| if !ok { |
| return nil, verror.New(verror.ErrUnknownMethod, ctx, method) |
| } |
| // Create the reflect.Value args for the invocation. The receiver of the |
| // method is always first, followed by the required ctx and call args. |
| rvArgs := make([]reflect.Value, len(argptrs)+3) |
| rvArgs[0] = ri.rcvr |
| rvArgs[1] = reflect.ValueOf(ctx) |
| if info.rtStreamCall == nil { |
| // There isn't a typesafe streaming call, just use the call. |
| rvArgs[2] = reflect.ValueOf(call) |
| } else { |
| // There is a typesafe streaming call with type rtStreamCall. We perform |
| // the equivalent of the following: |
| // ctx := new(rtStreamCall) |
| // ctx.Init(call) |
| rvStreamCall := reflect.New(info.rtStreamCall) |
| info.rvStreamCallInit.Call([]reflect.Value{rvStreamCall, reflect.ValueOf(call)}) |
| rvArgs[2] = rvStreamCall |
| } |
| // Positional user args follow. |
| for ix, argptr := range argptrs { |
| rvArgs[ix+3] = reflect.ValueOf(argptr).Elem() |
| } |
| // Invoke the method, and handle the final error out-arg. |
| rvResults := info.rvFunc.Call(rvArgs) |
| if len(rvResults) == 0 { |
| return nil, abortedf(errNoFinalErrorOutArg) |
| } |
| rvErr := rvResults[len(rvResults)-1] |
| rvResults = rvResults[:len(rvResults)-1] |
| if rvErr.Type() != rtError { |
| return nil, abortedf(errNoFinalErrorOutArg) |
| } |
| if iErr := rvErr.Interface(); iErr != nil { |
| return nil, iErr.(error) |
| } |
| // Convert the rest of the results into interface{}. |
| if len(rvResults) == 0 { |
| return nil, nil |
| } |
| results := make([]interface{}, len(rvResults)) |
| for ix, r := range rvResults { |
| results[ix] = r.Interface() |
| } |
| return results, nil |
| } |
| |
| // Signature implements the Invoker.Signature method. |
| func (ri reflectInvoker) Signature(ctx *context.T, call ServerCall) ([]signature.Interface, error) { |
| return signature.CopyInterfaces(ri.sig), nil |
| } |
| |
| // MethodSignature implements the Invoker.MethodSignature method. |
| func (ri reflectInvoker) MethodSignature(ctx *context.T, call ServerCall, method string) (signature.Method, error) { |
| // Return the first method in any interface with the given method name. |
| for _, iface := range ri.sig { |
| if msig, ok := iface.FindMethod(method); ok { |
| return signature.CopyMethod(msig), nil |
| } |
| } |
| return signature.Method{}, verror.New(verror.ErrUnknownMethod, ctx, method) |
| } |
| |
| // Globber implements the rpc.Globber interface. |
| func (ri reflectInvoker) Globber() *GlobState { |
| return determineGlobState(ri.rcvr.Interface()) |
| } |
| |
| // reflectRegistry is a locked map from reflect.Type to reflection info, which |
| // is expensive to compute. The only instance is reflectCache, which is a |
| // global cache to speed up repeated lookups. There is no GC; the total set of |
| // types in a single address space is expected to be bounded and small. |
| type reflectRegistry struct { |
| sync.RWMutex |
| infoMap map[reflect.Type]*reflectInfo |
| } |
| |
| type reflectInfo struct { |
| methods map[string]methodInfo |
| sig []signature.Interface |
| } |
| |
| func (reg *reflectRegistry) lookup(rt reflect.Type) *reflectInfo { |
| reg.RLock() |
| info := reg.infoMap[rt] |
| reg.RUnlock() |
| return info |
| } |
| |
| // set the entry for (rt, info). Is a no-op if rt already exists in the map. |
| func (reg *reflectRegistry) set(rt reflect.Type, info *reflectInfo) { |
| reg.Lock() |
| if exist := reg.infoMap[rt]; exist == nil { |
| reg.infoMap[rt] = info |
| } |
| reg.Unlock() |
| } |
| |
| var reflectCache = &reflectRegistry{infoMap: make(map[reflect.Type]*reflectInfo)} |
| |
| // newReflectInfo returns reflection information that is expensive to compute. |
| // Although it is passed an object rather than a type, it guarantees that the |
| // returned information is always the same for all instances of a given type. |
| func newReflectInfo(obj interface{}) (*reflectInfo, error) { |
| if obj == nil { |
| return nil, verror.New(errReflectInvokerNil, nil) |
| } |
| // First make methodInfos, based on reflect.Type, which also captures the name |
| // and in, out and streaming types of each method in methodSigs. This |
| // information is guaranteed to be correct, since it's based on reflection on |
| // the underlying object. |
| rt := reflect.TypeOf(obj) |
| methodInfos, methodSigs, err := makeMethods(rt) |
| switch { |
| case err != nil: |
| return nil, err |
| case len(methodInfos) == 0 && determineGlobState(obj) == nil: |
| if m := TypeCheckMethods(obj); len(m) > 0 { |
| return nil, verror.New(errNoCompatibleMethods, nil, rt, TypeCheckMethods(obj)) |
| } |
| return nil, verror.New(errNoCompatibleMethods, nil, rt, "no exported methods") |
| } |
| // Now attach method tags to each methodInfo. Since this is based on the desc |
| // provided by the user, there's no guarantee it's "correct", but if the same |
| // method is described by multiple interfaces, we check the tags are the same. |
| desc := describe(obj) |
| if err := attachMethodTags(methodInfos, desc); verror.ErrorID(err) == verror.ErrAborted.ID { |
| return nil, verror.New(errTagError, nil, rt, err) |
| } |
| // Finally create the signature. This combines the desc provided by the user |
| // with the methodSigs computed via reflection. We ensure that the method |
| // names and types computed via reflection always remains in the final sig; |
| // the desc is merely used to augment the signature. |
| sig := makeSig(desc, methodSigs) |
| return &reflectInfo{methodInfos, sig}, nil |
| } |
| |
| // determineGlobState determines whether and how obj implements Glob. Returns |
| // nil iff obj doesn't implement Glob, based solely on the type of obj. |
| func determineGlobState(obj interface{}) *GlobState { |
| if x, ok := obj.(Globber); ok { |
| return x.Globber() |
| } |
| return NewGlobState(obj) |
| } |
| |
| func describe(obj interface{}) []InterfaceDesc { |
| if d, ok := obj.(Describer); ok { |
| // Describe__ must not vary across instances of the same underlying type. |
| return d.Describe__() |
| } |
| return nil |
| } |
| |
| func makeMethods(rt reflect.Type) (map[string]methodInfo, map[string]signature.Method, error) { |
| infos := make(map[string]methodInfo, rt.NumMethod()) |
| sigs := make(map[string]signature.Method, rt.NumMethod()) |
| for mx := 0; mx < rt.NumMethod(); mx++ { |
| method := rt.Method(mx) |
| // Silently skip incompatible methods, except for Aborted errors. |
| var sig signature.Method |
| if err := typeCheckMethod(method, &sig); err != nil { |
| if verror.ErrorID(err) == verror.ErrAborted.ID { |
| return nil, nil, verror.New(errAbortedDetail, nil, rt.String(), method.Name, err) |
| } |
| continue |
| } |
| infos[method.Name] = makeMethodInfo(method) |
| sigs[method.Name] = sig |
| } |
| return infos, sigs, nil |
| } |
| |
| func makeMethodInfo(method reflect.Method) methodInfo { |
| info := methodInfo{rvFunc: method.Func} |
| mtype := method.Type |
| for ix := 3; ix < mtype.NumIn(); ix++ { // Skip receiver, ctx and call |
| info.rtInArgs = append(info.rtInArgs, mtype.In(ix)) |
| } |
| // Initialize info for typesafe streaming calls. Note that we've already |
| // type-checked the method. We memoize the stream type and Init function, so |
| // that we can create and initialize the stream type in Invoke. |
| if rt := mtype.In(2); rt != rtStreamServerCall && rt != rtServerCall && rt.Kind() == reflect.Ptr { |
| info.rtStreamCall = rt.Elem() |
| mInit, _ := rt.MethodByName("Init") |
| info.rvStreamCallInit = mInit.Func |
| } |
| return info |
| } |
| |
| func abortedf(embeddedErr verror.IDAction, v ...interface{}) error { |
| return verror.New(verror.ErrAborted, nil, verror.New(embeddedErr, nil, v...)) |
| } |
| |
| const ( |
| pkgPath = "v.io/v23/rpc" |
| useCall = " Use either rpc.ServerCall for non-streaming methods, or use a non-interface typesafe call for streaming methods." |
| forgotWrap = useCall + " Perhaps you forgot to wrap your server with the VDL-generated server stub." |
| ) |
| |
| var ( |
| rtPtrToContext = reflect.TypeOf((*context.T)(nil)) |
| rtStreamServerCall = reflect.TypeOf((*StreamServerCall)(nil)).Elem() |
| rtServerCall = reflect.TypeOf((*ServerCall)(nil)).Elem() |
| rtGlobServerCall = reflect.TypeOf((*GlobServerCall)(nil)).Elem() |
| rtGlobChildrenServerCall = reflect.TypeOf((*GlobChildrenServerCall)(nil)).Elem() |
| rtBool = reflect.TypeOf(bool(false)) |
| rtError = reflect.TypeOf((*error)(nil)).Elem() |
| rtPtrToGlobState = reflect.TypeOf((*GlobState)(nil)) |
| rtSliceOfInterfaceDesc = reflect.TypeOf([]InterfaceDesc{}) |
| rtPtrToGlobGlob = reflect.TypeOf((*glob.Glob)(nil)) |
| rtPtrToGlobElement = reflect.TypeOf((*glob.Element)(nil)) |
| |
| // ReflectInvoker will panic iff the error is Aborted, otherwise it will |
| // silently ignore the error. |
| |
| // These errors are not embedded in other errors. |
| errReflectInvokerNil = verror.Register(pkgPath+".errReflectInvokerNil", verror.NoRetry, "{1:}{2:}rpc: ReflectInvoker(nil) is invalid{:_}") |
| errNoCompatibleMethods = verror.Register(pkgPath+".errNoCompatibleMethods", verror.NoRetry, "{1:}{2:}rpc: type {3} has no compatible methods{:_}") |
| errTagError = verror.Register(pkgPath+".errTagError", verror.NoRetry, "{1:}{2:}rpc: type {3} tag error{:_}") |
| errAbortedDetail = verror.Register(pkgPath+".errAbortedDetail", verror.NoRetry, "{1:}{2:}rpc: type {3}.{4}{:_}") |
| |
| // These errors are embedded in verror.ErrInternal: |
| errReservedMethod = verror.Register(pkgPath+".errReservedMethod", verror.NoRetry, "{1:}{2:}Reserved method{:_}") |
| |
| // These errors are embedded in verror.ErrBadArg: |
| errMethodNotExported = verror.Register(pkgPath+".errMethodNotExported", verror.NoRetry, "{1:}{2:}Method not exported{:_}") |
| errNonRPCMethod = verror.Register(pkgPath+".errNonRPCMethod", verror.NoRetry, "{1:}{2:}Non-rpc method, at least 2 in-args are required, with first arg *context.T."+useCall+"{:_}") |
| |
| // These errors are expected to be embedded in verror.Aborted, via abortedf(): |
| errInStreamServerCall = verror.Register(pkgPath+".errInStreamServerCall", verror.NoRetry, "{1:}{2:}Call arg rpc.StreamServerCall is invalid; cannot determine streaming types."+forgotWrap+"{:_}") |
| errNoFinalErrorOutArg = verror.Register(pkgPath+".errNoFinalErrorOutArg", verror.NoRetry, "{1:}{2:}Invalid out-args (final out-arg must be error){:_}") |
| errBadDescribe = verror.Register(pkgPath+".errBadDescribe", verror.NoRetry, "{1:}{2:}Describe__ must have signature Describe__() []rpc.InterfaceDesc{:_}") |
| errBadGlobber = verror.Register(pkgPath+".errBadGlobber", verror.NoRetry, "{1:}{2:}Globber must have signature Globber() *rpc.GlobState{:_}") |
| errBadGlob = verror.Register(pkgPath+".errBadGlob", verror.NoRetry, "{1:}{2:}Glob__ must have signature Glob__(ctx *context.T, call GlobServerCall, g *glob.Glob) error{:_}") |
| errBadGlobChildren = verror.Register(pkgPath+".errBadGlobChildren", verror.NoRetry, "{1:}{2:}GlobChildren__ must have signature GlobChildren__(ctx *context.T, call GlobChildrenServerCall, matcher *glob.Element) error{:_}") |
| |
| errNeedStreamingCall = verror.Register(pkgPath+".errNeedStreamingCall", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; must be pointer to a struct representing the typesafe streaming call."+forgotWrap+"{:_}") |
| errNeedInitMethod = verror.Register(pkgPath+".errNeedInitMethod", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; must have Init method."+forgotWrap+"{:_}") |
| errNeedSigFunc = verror.Register(pkgPath+".errNeedNeedSigFunc", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; Init must have signature func (*) Init(rpc.StreamServerCall)."+forgotWrap+"{:_}") |
| errNeedStreamMethod = verror.Register(pkgPath+".errNeedStreamMethod", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; must have at least one of RecvStream or SendStream methods."+forgotWrap+"{:_}") |
| errInvalidInStream = verror.Register(pkgPath+".errInvalidInStream", verror.NoRetry, "{1:}{2:}Invalid in-stream type{:_}") |
| errInvalidOutStream = verror.Register(pkgPath+".errInvalidOutStream", verror.NoRetry, "{1:}{2:}Invalid out-stream type{:_}") |
| errNeedRecvStreamSignature = verror.Register(pkgPath+".errNeedRecvStreamSignature", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; RecvStream must have signature func (*) RecvStream() interface{ Advance() bool; Value() _; Err() error }."+forgotWrap+"{:_}") |
| errNeedSendStreamSignature = verror.Register(pkgPath+".errNeedSendStreamSignature", verror.NoRetry, "{1:}{2:}Call arg %s is invalid streaming call; SendStream must have signature func (*) SendStream() interface{ Send(_) error }."+forgotWrap+"{:_}") |
| errInvalidInArg = verror.Register(pkgPath+".errInvalidInArg", verror.NoRetry, "{1:}{2:}Invalid in-arg {3} type{:_}") |
| errInvalidOutArg = verror.Register(pkgPath+".errInvalidOutArg", verror.NoRetry, "{1:}{2:}Invalid out-arg {3} type{:_}") |
| errDifferentTags = verror.Register(pkgPath+".errDifferentTags", verror.NoRetry, "{1:}{2:}different tags {3} and {4}{:_}") |
| errUnknown = verror.Register(pkgPath+".errUnknown", verror.NoRetry, "{1:}{2:}method {3}{:_}") |
| ) |
| |
| func typeCheckMethod(method reflect.Method, sig *signature.Method) error { |
| if err := typeCheckReservedMethod(method); err != nil { |
| return err |
| } |
| // Unexported methods always have a non-empty pkg path. |
| if method.PkgPath != "" { |
| return verror.New(verror.ErrBadArg, nil, verror.New(errMethodNotExported, nil)) |
| } |
| sig.Name = method.Name |
| mtype := method.Type |
| // Method must have at least 3 in args (receiver, ctx, call). |
| if in := mtype.NumIn(); in < 3 || mtype.In(1) != rtPtrToContext { |
| return verror.New(verror.ErrBadArg, nil, verror.New(errNonRPCMethod, nil)) |
| } |
| switch in2 := mtype.In(2); { |
| case in2 == rtStreamServerCall: |
| // If the second call arg is rpc.StreamServerCall, we do not know whether |
| // the method performs streaming, or what the stream types are. |
| sig.InStream = &signature.Arg{Type: vdl.AnyType} |
| sig.OutStream = &signature.Arg{Type: vdl.AnyType} |
| // We can either disallow rpc.StreamServerCall, at the expense of more boilerplate |
| // for users that don't use the VDL but want to perform streaming. Or we |
| // can allow it, but won't be able to determine whether the server uses the |
| // stream, or what the streaming types are. |
| // |
| // At the moment we allow it; we can easily disallow by enabling this error. |
| // return abortedf(errInStreamServerCall) |
| case in2 == rtServerCall: |
| // Non-streaming method. |
| case in2.Implements(rtServerCall): |
| // Streaming method, validate call argument. |
| if err := typeCheckStreamingCall(in2, sig); err != nil { |
| return err |
| } |
| default: |
| return verror.New(verror.ErrBadArg, nil, verror.New(errNonRPCMethod, nil)) |
| } |
| return typeCheckMethodArgs(mtype, sig) |
| } |
| |
| func typeCheckReservedMethod(method reflect.Method) error { |
| switch method.Name { |
| case "Describe__": |
| // Describe__() []InterfaceDesc |
| if t := method.Type; t.NumIn() != 1 || t.NumOut() != 1 || |
| t.Out(0) != rtSliceOfInterfaceDesc { |
| return abortedf(errBadDescribe) |
| } |
| return verror.New(verror.ErrInternal, nil, verror.New(errReservedMethod, nil)) |
| case "Globber": |
| // Globber() *GlobState |
| if t := method.Type; t.NumIn() != 1 || t.NumOut() != 1 || |
| t.Out(0) != rtPtrToGlobState { |
| return abortedf(errBadGlobber) |
| } |
| return verror.New(verror.ErrInternal, nil, verror.New(errReservedMethod, nil)) |
| case "Glob__": |
| // Glob__(ctx *context.T, call GlobServerCall, g *glob.Glob) error |
| if t := method.Type; t.NumIn() != 4 || t.NumOut() != 1 || |
| t.In(1) != rtPtrToContext || t.In(2) != rtGlobServerCall || t.In(3) != rtPtrToGlobGlob || |
| t.Out(0) != rtError { |
| return abortedf(errBadGlob) |
| } |
| return verror.New(verror.ErrInternal, nil, verror.New(errReservedMethod, nil)) |
| case "GlobChildren__": |
| // GlobChildren__(ctx *context.T, call GlobChildrenServerCall, matcher *glob.Element) error |
| if t := method.Type; t.NumIn() != 4 || t.NumOut() != 1 || |
| t.In(1) != rtPtrToContext || t.In(2) != rtGlobChildrenServerCall || t.In(3) != rtPtrToGlobElement || |
| t.Out(0) != rtError { |
| return abortedf(errBadGlobChildren) |
| } |
| return verror.New(verror.ErrInternal, nil, verror.New(errReservedMethod, nil)) |
| } |
| return nil |
| } |
| |
| func typeCheckStreamingCall(rtCall reflect.Type, sig *signature.Method) error { |
| // The call must be a pointer to a struct. |
| if rtCall.Kind() != reflect.Ptr || rtCall.Elem().Kind() != reflect.Struct { |
| return abortedf(errNeedStreamingCall, rtCall) |
| } |
| // Must have Init(rpc.StreamServerCall) method. |
| mInit, hasInit := rtCall.MethodByName("Init") |
| if !hasInit { |
| return abortedf(errNeedInitMethod, rtCall) |
| } |
| if t := mInit.Type; t.NumIn() != 2 || t.In(0).Kind() != reflect.Ptr || t.In(1) != rtStreamServerCall || t.NumOut() != 0 { |
| return abortedf(errNeedSigFunc, rtCall) |
| } |
| // Must have either RecvStream or SendStream method, or both. |
| mRecvStream, hasRecvStream := rtCall.MethodByName("RecvStream") |
| mSendStream, hasSendStream := rtCall.MethodByName("SendStream") |
| if !hasRecvStream && !hasSendStream { |
| return abortedf(errNeedStreamMethod, rtCall) |
| } |
| if hasRecvStream { |
| // func (*) RecvStream() interface{ Advance() bool; Value() _; Err() error } |
| tRecv := mRecvStream.Type |
| if tRecv.NumIn() != 1 || tRecv.In(0).Kind() != reflect.Ptr || |
| tRecv.NumOut() != 1 || tRecv.Out(0).Kind() != reflect.Interface { |
| return abortedf(errNeedRecvStreamSignature, rtCall) |
| } |
| mA, hasA := tRecv.Out(0).MethodByName("Advance") |
| mV, hasV := tRecv.Out(0).MethodByName("Value") |
| mE, hasE := tRecv.Out(0).MethodByName("Err") |
| tA, tV, tE := mA.Type, mV.Type, mE.Type |
| if !hasA || !hasV || !hasE || |
| tA.NumIn() != 0 || tA.NumOut() != 1 || tA.Out(0) != rtBool || |
| tV.NumIn() != 0 || tV.NumOut() != 1 || // tV.Out(0) is in-stream type |
| tE.NumIn() != 0 || tE.NumOut() != 1 || tE.Out(0) != rtError { |
| return abortedf(errNeedRecvStreamSignature, rtCall) |
| } |
| inType, err := vdl.TypeFromReflect(tV.Out(0)) |
| if err != nil { |
| return abortedf(errInvalidInStream, err) |
| } |
| sig.InStream = &signature.Arg{Type: inType} |
| } |
| if hasSendStream { |
| // func (*) SendStream() interface{ Send(_) error } |
| tSend := mSendStream.Type |
| if tSend.NumIn() != 1 || tSend.In(0).Kind() != reflect.Ptr || |
| tSend.NumOut() != 1 || tSend.Out(0).Kind() != reflect.Interface { |
| return abortedf(errNeedSendStreamSignature, rtCall) |
| } |
| mS, hasS := tSend.Out(0).MethodByName("Send") |
| tS := mS.Type |
| if !hasS || |
| tS.NumIn() != 1 || // tS.In(0) is out-stream type |
| tS.NumOut() != 1 || tS.Out(0) != rtError { |
| return abortedf(errNeedSendStreamSignature, rtCall) |
| } |
| outType, err := vdl.TypeFromReflect(tS.In(0)) |
| if err != nil { |
| return abortedf(errInvalidOutStream, err) |
| } |
| sig.OutStream = &signature.Arg{Type: outType} |
| } |
| return nil |
| } |
| |
| func typeCheckMethodArgs(mtype reflect.Type, sig *signature.Method) error { |
| // Start in-args from 3 to skip receiver, ctx and call arguments. |
| for index := 3; index < mtype.NumIn(); index++ { |
| vdlType, err := vdl.TypeFromReflect(mtype.In(index)) |
| if err != nil { |
| return abortedf(errInvalidInArg, index, err) |
| } |
| (*sig).InArgs = append((*sig).InArgs, signature.Arg{Type: vdlType}) |
| } |
| // The out-args must contain a final error argument, which is handled |
| // specially by the framework. |
| if mtype.NumOut() == 0 || mtype.Out(mtype.NumOut()-1) != rtError { |
| return abortedf(errNoFinalErrorOutArg) |
| } |
| for index := 0; index < mtype.NumOut()-1; index++ { |
| vdlType, err := vdl.TypeFromReflect(mtype.Out(index)) |
| if err != nil { |
| return abortedf(errInvalidOutArg, index, err) |
| } |
| (*sig).OutArgs = append((*sig).OutArgs, signature.Arg{Type: vdlType}) |
| } |
| return nil |
| } |
| |
| func makeSig(desc []InterfaceDesc, methods map[string]signature.Method) []signature.Interface { |
| var sig []signature.Interface |
| used := make(map[string]bool, len(methods)) |
| // Loop through the user-provided desc, attaching descriptions to the actual |
| // method types to create our final signatures. Ignore user-provided |
| // descriptions of interfaces or methods that don't exist. |
| for _, descIface := range desc { |
| var sigMethods []signature.Method |
| for _, descMethod := range descIface.Methods { |
| sigMethod, ok := methods[descMethod.Name] |
| if ok { |
| // The method name and all types are already populated in sigMethod; |
| // fill in the rest of the description. |
| sigMethod.Doc = descMethod.Doc |
| sigMethod.InArgs = makeArgSigs(sigMethod.InArgs, descMethod.InArgs) |
| sigMethod.OutArgs = makeArgSigs(sigMethod.OutArgs, descMethod.OutArgs) |
| sigMethod.InStream = fillArgSig(sigMethod.InStream, descMethod.InStream) |
| sigMethod.OutStream = fillArgSig(sigMethod.OutStream, descMethod.OutStream) |
| sigMethod.Tags = descMethod.Tags |
| sigMethods = append(sigMethods, sigMethod) |
| used[sigMethod.Name] = true |
| } |
| } |
| if len(sigMethods) > 0 { |
| sort.Sort(signature.SortableMethods(sigMethods)) |
| sigIface := signature.Interface{ |
| Name: descIface.Name, |
| PkgPath: descIface.PkgPath, |
| Doc: descIface.Doc, |
| Methods: sigMethods, |
| } |
| for _, descEmbed := range descIface.Embeds { |
| sigEmbed := signature.Embed{ |
| Name: descEmbed.Name, |
| PkgPath: descEmbed.PkgPath, |
| Doc: descEmbed.Doc, |
| } |
| sigIface.Embeds = append(sigIface.Embeds, sigEmbed) |
| } |
| sig = append(sig, sigIface) |
| } |
| } |
| // Add all unused methods into the catch-all empty interface. |
| var unusedMethods []signature.Method |
| for _, method := range methods { |
| if !used[method.Name] { |
| unusedMethods = append(unusedMethods, method) |
| } |
| } |
| if len(unusedMethods) > 0 { |
| const unusedDoc = "The empty interface contains methods not attached to any interface." |
| sort.Sort(signature.SortableMethods(unusedMethods)) |
| sig = append(sig, signature.Interface{Doc: unusedDoc, Methods: unusedMethods}) |
| } |
| return sig |
| } |
| |
| func makeArgSigs(sigs []signature.Arg, descs []ArgDesc) []signature.Arg { |
| result := make([]signature.Arg, len(sigs)) |
| for index, sig := range sigs { |
| if index < len(descs) { |
| sig = *fillArgSig(&sig, descs[index]) |
| } |
| result[index] = sig |
| } |
| return result |
| } |
| |
| func fillArgSig(sig *signature.Arg, desc ArgDesc) *signature.Arg { |
| if sig == nil { |
| return nil |
| } |
| ret := *sig |
| ret.Name = desc.Name |
| ret.Doc = desc.Doc |
| return &ret |
| } |
| |
| // extractTagsForMethod returns the tags associated with the given method name. |
| // If the desc lists the same method under multiple interfaces, we require all |
| // versions to have an identical list of tags. |
| func extractTagsForMethod(desc []InterfaceDesc, name string) ([]*vdl.Value, error) { |
| seenFirst := false |
| var first []*vdl.Value |
| for _, descIface := range desc { |
| for _, descMethod := range descIface.Methods { |
| if name == descMethod.Name { |
| switch tags := descMethod.Tags; { |
| case !seenFirst: |
| seenFirst = true |
| first = tags |
| case !equalTags(first, tags): |
| return nil, abortedf(errDifferentTags, first, tags) |
| } |
| } |
| } |
| } |
| return first, nil |
| } |
| |
| func equalTags(a, b []*vdl.Value) bool { |
| if len(a) != len(b) { |
| return false |
| } |
| for i := 0; i < len(a); i++ { |
| if !vdl.EqualValue(a[i], b[i]) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // attachMethodTags sets methodInfo.tags to the tags that will be returned in |
| // Prepare. This also performs type checking on the tags. |
| func attachMethodTags(infos map[string]methodInfo, desc []InterfaceDesc) error { |
| for name, info := range infos { |
| tags, err := extractTagsForMethod(desc, name) |
| if err != nil { |
| return abortedf(errUnknown, name, err) |
| } |
| info.tags = tags |
| infos[name] = info |
| } |
| return nil |
| } |
| |
| // TypeCheckMethods type checks each method in obj, and returns a map from |
| // method name to the type check result. Nil errors indicate the method is |
| // invocable by the Invoker returned by ReflectInvoker(obj). Non-nil errors |
| // contain details of the type mismatch - any error with the "Aborted" id will |
| // cause a panic in a ReflectInvoker() call. |
| // |
| // This is useful for debugging why a particular method isn't available via |
| // ReflectInvoker. |
| func TypeCheckMethods(obj interface{}) map[string]error { |
| rt, desc := reflect.TypeOf(obj), describe(obj) |
| var check map[string]error |
| if rt != nil && rt.NumMethod() > 0 { |
| check = make(map[string]error, rt.NumMethod()) |
| for mx := 0; mx < rt.NumMethod(); mx++ { |
| method := rt.Method(mx) |
| var sig signature.Method |
| err := typeCheckMethod(method, &sig) |
| if err == nil { |
| _, err = extractTagsForMethod(desc, method.Name) |
| } |
| check[method.Name] = err |
| } |
| } |
| return check |
| } |