Merge "Expand errors that can be returned from RPCs" into studio-master-dev
diff --git a/builder/follow.go b/builder/follow.go
index 888ef00..13cd218 100644
--- a/builder/follow.go
+++ b/builder/follow.go
@@ -29,20 +29,20 @@
 	if err != nil {
 		return nil, err
 	}
-	if linker, ok := obj.(path.Linker); ok {
-		link, err := linker.Link(ctx, r.Path, d)
-		if err != nil {
-			return link, fmt.Errorf("Following path %v gave an error", r.Path, err)
-		}
-		if link == nil {
-			return nil, fmt.Errorf("Following path %v gave a nil link", r.Path)
-		}
-		err = link.Validate()
-		if err != nil {
-			return nil, fmt.Errorf("Following path %v gave an invalid link %v: %v",
-				r.Path, link, err)
-		}
+	linker, ok := obj.(path.Linker)
+	if !ok {
+		return nil, &path.ErrNotFollowable{Path: r.Path}
+	}
+	link, err := linker.Link(ctx, r.Path, d)
+	if err != nil {
 		return link, err
 	}
-	return nil, fmt.Errorf("Path %v can not be followed", r.Path)
+	if link == nil {
+		return nil, fmt.Errorf("Following path %v gave a nil link", r.Path)
+	}
+	if err := link.Validate(); err != nil {
+		return nil, fmt.Errorf("Following path %v gave an invalid link %v: %v",
+			r.Path, link, err)
+	}
+	return link, nil
 }
diff --git a/gfxapi/gles/links.go b/gfxapi/gles/links.go
index 48fb889..5ed748d 100644
--- a/gfxapi/gles/links.go
+++ b/gfxapi/gles/links.go
@@ -15,8 +15,6 @@
 package gles
 
 import (
-	"fmt"
-
 	"android.googlesource.com/platform/tools/gpu/atom"
 	"android.googlesource.com/platform/tools/gpu/builder"
 	"android.googlesource.com/platform/tools/gpu/database"
@@ -38,7 +36,7 @@
 			MapIndex(uint64(state.CurrentThread)).
 			Field("Instances"), nil
 	}
-	return nil, fmt.Errorf("Path %v is not a path to an atom", p)
+	return nil, &path.ErrNotFollowable{Path: p}
 }
 
 func (o AttributeLocation) Link(ctx log.Context, p path.Path, d database.Database) (path.Path, error) {
@@ -136,7 +134,7 @@
 			Field("Uniforms").
 			MapIndex(int64(o)), nil
 	} else {
-		return nil, fmt.Errorf("Path %v is not a path to an atom", p)
+		return nil, &path.ErrNotFollowable{Path: p}
 	}
 }
 
diff --git a/rpc/client.go b/rpc/client.go
index cda8773..1126948 100644
--- a/rpc/client.go
+++ b/rpc/client.go
@@ -72,7 +72,7 @@
 	}
 
 	// Write the RPC header
-	if e.Data(header[:]); d.Error() != nil {
+	if e.Data(headerV1[:]); d.Error() != nil {
 		return nil, d.Error()
 	}
 
@@ -93,7 +93,7 @@
 	}
 
 	// Check to see if the response was an error
-	if err, b := res.(*Error); b {
+	if err, b := res.(Err); b {
 		return nil, err
 	}
 
diff --git a/rpc/error.go b/rpc/error.go
new file mode 100644
index 0000000..341abab
--- /dev/null
+++ b/rpc/error.go
@@ -0,0 +1,78 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rpc
+
+import (
+	"fmt"
+
+	"android.googlesource.com/platform/tools/gpu/binary"
+)
+
+// Err is the interface implemented by errors that can be sent over the wire.
+type Err interface {
+	binary.Object
+	Error() string
+}
+
+// Error is an implementation of Err that holds a single string error message.
+type Error struct {
+	binary.Generate `implements:"rpc.Err" java:"RpcError"`
+	Msg             string
+}
+
+func (e *Error) Error() string { return e.Msg }
+
+// ErrInvalidHeader is the error returned when the RPC call begins with an
+// invalid header code.
+type ErrInvalidHeader struct {
+	binary.Generate `implements:"rpc.Err"`
+	Header          [4]byte
+}
+
+func (e ErrInvalidHeader) Error() string {
+	return fmt.Sprintf("Invalid RPC header: %v", e.Header)
+}
+
+// ErrDecodingCall is the error returned when a call cannot be decoded by the
+// server.
+type ErrDecodingCall struct {
+	binary.Generate `implements:"rpc.Err"`
+	Reason          string
+}
+
+func (e ErrDecodingCall) Error() string {
+	return fmt.Sprintf("Error decoding RPC call: %v", e.Reason)
+}
+
+// ErrUnknownFunction is the error returned when a call is made to a function
+// unrecognised by the server.
+type ErrUnknownFunction struct {
+	binary.Generate `implements:"rpc.Err"`
+	Function        string
+}
+
+func (e ErrUnknownFunction) Error() string {
+	return fmt.Sprintf("Unknown RPC function: %v", e.Function)
+}
+
+// ErrPanic is the error returned when an RPC call raises an uncaught panic.
+type ErrPanic struct {
+	binary.Generate `implements:"rpc.Err"`
+	Msg             string
+}
+
+func (e ErrPanic) Error() string {
+	return fmt.Sprintf("RPC panic: %v", e.Msg)
+}
diff --git a/rpc/rpc.go b/rpc/rpc.go
index 708df67..0161715 100644
--- a/rpc/rpc.go
+++ b/rpc/rpc.go
@@ -23,27 +23,7 @@
 // binary: java.indent = "    "
 // binary: java.member_prefix = m
 
-import (
-	"fmt"
-
-	"android.googlesource.com/platform/tools/gpu/binary"
+var (
+	headerV0 = [4]byte{'r', 'p', 'c', '0'}
+	headerV1 = [4]byte{'r', 'p', 'c', '1'}
 )
-
-var header = [4]byte{'r', 'p', 'c', '0'}
-
-// ErrInvalidHeader is returned when either client or server detects an
-// incorrectly formed rpc header.
-var ErrInvalidHeader = NewError("Invalid RPC header")
-
-// NewError is used to create new rpc error objects with the specified human readable message.
-func NewError(msg string, args ...interface{}) *Error {
-	return &Error{message: fmt.Sprintf(msg, args...)}
-}
-
-// Error is an implementation of error that can be sent over the wire.
-type Error struct {
-	binary.Generate `java:"RpcError"`
-	message         string
-}
-
-func (e *Error) Error() string { return e.message }
diff --git a/rpc/rpc_binary.go b/rpc/rpc_binary.go
index 5ba8b97..f24ba0c 100644
--- a/rpc/rpc_binary.go
+++ b/rpc/rpc_binary.go
@@ -14,28 +14,161 @@
 import _ "android.googlesource.com/platform/tools/gpu/binary/schema"
 
 const (
-	ixǁError = iota
+	ixǁErrDecodingCall = iota
+	ixǁErrInvalidHeader
+	ixǁErrPanic
+	ixǁErrUnknownFunction
+	ixǁError
 )
 
-var entities [1]binary.Entity
+var entities [5]binary.Entity
 
 var Namespace = registry.NewNamespace()
 
 func init() {
 	registry.Global.AddFallbacks(Namespace)
+	Namespace.AddClassOf((*ErrDecodingCall)(nil))
+	Namespace.AddClassOf((*ErrInvalidHeader)(nil))
+	Namespace.AddClassOf((*ErrPanic)(nil))
+	Namespace.AddClassOf((*ErrUnknownFunction)(nil))
 	Namespace.AddClassOf((*Error)(nil))
 }
 
+var _ Err = (*ErrDecodingCall)(nil) // Interface compliance check.
+type binaryClassErrDecodingCall struct{}
+
+func (*ErrDecodingCall) Class() binary.Class {
+	return (*binaryClassErrDecodingCall)(nil)
+}
+func doEncodeErrDecodingCall(e binary.Writer, o *ErrDecodingCall) {
+	e.String(o.Reason)
+}
+func doDecodeErrDecodingCall(d binary.Reader, o *ErrDecodingCall) {
+	o.Reason = string(d.String())
+}
+func (*binaryClassErrDecodingCall) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeErrDecodingCall(e, obj.(*ErrDecodingCall))
+}
+func (*binaryClassErrDecodingCall) New() binary.Object {
+	return &ErrDecodingCall{}
+}
+func (*binaryClassErrDecodingCall) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeErrDecodingCall(d, obj.(*ErrDecodingCall))
+}
+func (o *ErrDecodingCall) WriteSimple(w binary.Writer) {
+	doEncodeErrDecodingCall(w, o)
+}
+func (o *ErrDecodingCall) ReadSimple(r binary.Reader) {
+	doDecodeErrDecodingCall(r, o)
+}
+func (c *binaryClassErrDecodingCall) Schema() *binary.Entity {
+	return &entities[ixǁErrDecodingCall]
+}
+
+var _ Err = (*ErrInvalidHeader)(nil) // Interface compliance check.
+type binaryClassErrInvalidHeader struct{}
+
+func (*ErrInvalidHeader) Class() binary.Class {
+	return (*binaryClassErrInvalidHeader)(nil)
+}
+func doEncodeErrInvalidHeader(e binary.Writer, o *ErrInvalidHeader) {
+	e.Data(o.Header[:4])
+}
+func doDecodeErrInvalidHeader(d binary.Reader, o *ErrInvalidHeader) {
+	d.Data(o.Header[:4])
+}
+func (*binaryClassErrInvalidHeader) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeErrInvalidHeader(e, obj.(*ErrInvalidHeader))
+}
+func (*binaryClassErrInvalidHeader) New() binary.Object {
+	return &ErrInvalidHeader{}
+}
+func (*binaryClassErrInvalidHeader) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeErrInvalidHeader(d, obj.(*ErrInvalidHeader))
+}
+func (o *ErrInvalidHeader) WriteSimple(w binary.Writer) {
+	doEncodeErrInvalidHeader(w, o)
+}
+func (o *ErrInvalidHeader) ReadSimple(r binary.Reader) {
+	doDecodeErrInvalidHeader(r, o)
+}
+func (c *binaryClassErrInvalidHeader) Schema() *binary.Entity {
+	return &entities[ixǁErrInvalidHeader]
+}
+
+var _ Err = (*ErrPanic)(nil) // Interface compliance check.
+type binaryClassErrPanic struct{}
+
+func (*ErrPanic) Class() binary.Class {
+	return (*binaryClassErrPanic)(nil)
+}
+func doEncodeErrPanic(e binary.Writer, o *ErrPanic) {
+	e.String(o.Msg)
+}
+func doDecodeErrPanic(d binary.Reader, o *ErrPanic) {
+	o.Msg = string(d.String())
+}
+func (*binaryClassErrPanic) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeErrPanic(e, obj.(*ErrPanic))
+}
+func (*binaryClassErrPanic) New() binary.Object {
+	return &ErrPanic{}
+}
+func (*binaryClassErrPanic) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeErrPanic(d, obj.(*ErrPanic))
+}
+func (o *ErrPanic) WriteSimple(w binary.Writer) {
+	doEncodeErrPanic(w, o)
+}
+func (o *ErrPanic) ReadSimple(r binary.Reader) {
+	doDecodeErrPanic(r, o)
+}
+func (c *binaryClassErrPanic) Schema() *binary.Entity {
+	return &entities[ixǁErrPanic]
+}
+
+var _ Err = (*ErrUnknownFunction)(nil) // Interface compliance check.
+type binaryClassErrUnknownFunction struct{}
+
+func (*ErrUnknownFunction) Class() binary.Class {
+	return (*binaryClassErrUnknownFunction)(nil)
+}
+func doEncodeErrUnknownFunction(e binary.Writer, o *ErrUnknownFunction) {
+	e.String(o.Function)
+}
+func doDecodeErrUnknownFunction(d binary.Reader, o *ErrUnknownFunction) {
+	o.Function = string(d.String())
+}
+func (*binaryClassErrUnknownFunction) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeErrUnknownFunction(e, obj.(*ErrUnknownFunction))
+}
+func (*binaryClassErrUnknownFunction) New() binary.Object {
+	return &ErrUnknownFunction{}
+}
+func (*binaryClassErrUnknownFunction) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeErrUnknownFunction(d, obj.(*ErrUnknownFunction))
+}
+func (o *ErrUnknownFunction) WriteSimple(w binary.Writer) {
+	doEncodeErrUnknownFunction(w, o)
+}
+func (o *ErrUnknownFunction) ReadSimple(r binary.Reader) {
+	doDecodeErrUnknownFunction(r, o)
+}
+func (c *binaryClassErrUnknownFunction) Schema() *binary.Entity {
+	return &entities[ixǁErrUnknownFunction]
+}
+
+var _ Err = (*Error)(nil) // Interface compliance check.
 type binaryClassError struct{}
 
 func (*Error) Class() binary.Class {
 	return (*binaryClassError)(nil)
 }
 func doEncodeError(e binary.Writer, o *Error) {
-	e.String(o.message)
+	e.String(o.Msg)
 }
 func doDecodeError(d binary.Reader, o *Error) {
-	o.message = string(d.String())
+	o.Msg = string(d.String())
 }
 func (*binaryClassError) Encode(e binary.Encoder, obj binary.Object) {
 	doEncodeError(e, obj.(*Error))
diff --git a/rpc/rpc_binary_test.go b/rpc/rpc_binary_test.go
index a454fc4..6c81520 100644
--- a/rpc/rpc_binary_test.go
+++ b/rpc/rpc_binary_test.go
@@ -12,14 +12,16 @@
 
 const (
 	ixǁdelay = iota
+	ixǁerr
 	ixǁrequest
 	ixǁresponse
 )
 
-var test_entities [3]binary.Entity
+var test_entities [4]binary.Entity
 
 func init() {
 	Namespace.AddClassOf((*delay)(nil))
+	Namespace.AddClassOf((*err)(nil))
 	Namespace.AddClassOf((*request)(nil))
 	Namespace.AddClassOf((*response)(nil))
 }
@@ -54,6 +56,37 @@
 	return &test_entities[ixǁdelay]
 }
 
+var _ Err = (*err)(nil) // Interface compliance check.
+type binaryClasserr struct{}
+
+func (*err) Class() binary.Class {
+	return (*binaryClasserr)(nil)
+}
+func doEncodeerr(e binary.Writer, o *err) {
+	e.String(o.msg)
+}
+func doDecodeerr(d binary.Reader, o *err) {
+	o.msg = string(d.String())
+}
+func (*binaryClasserr) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeerr(e, obj.(*err))
+}
+func (*binaryClasserr) New() binary.Object {
+	return &err{}
+}
+func (*binaryClasserr) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeerr(d, obj.(*err))
+}
+func (o *err) WriteSimple(w binary.Writer) {
+	doEncodeerr(w, o)
+}
+func (o *err) ReadSimple(r binary.Reader) {
+	doDecodeerr(r, o)
+}
+func (c *binaryClasserr) Schema() *binary.Entity {
+	return &test_entities[ixǁerr]
+}
+
 type binaryClassrequest struct{}
 
 func (*request) Class() binary.Class {
diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go
index 7415e59..a14a9ba 100644
--- a/rpc/rpc_test.go
+++ b/rpc/rpc_test.go
@@ -15,7 +15,9 @@
 package rpc
 
 import (
+	"fmt"
 	"io"
+	"reflect"
 	"testing"
 
 	"android.googlesource.com/platform/tools/gpu/binary"
@@ -40,20 +42,27 @@
 	data string
 }
 
+type err struct {
+	binary.Generate `implements:"rpc.Err"`
+	msg             string
+}
+
+func (e err) Error() string { return e.msg }
+
 func create(t *testing.T) Client {
 	ctx := log.Testing(t)
 	pass := make(chan string, 1)
 	sr, cw := io.Pipe()
 	cr, sw := io.Pipe()
-	Serve(ctx, sr, sw, sw, mtu, func(ctx log.Context, call interface{}) binary.Object {
+	Serve(ctx, sr, sw, sw, mtu, func(ctx log.Context, call interface{}) (binary.Object, error) {
 		switch o := call.(type) {
 		case *request:
 			pass <- o.data
-			return &response{data: o.data}
+			return &response{data: o.data}, nil
 		case *delay:
-			return &response{data: <-pass}
+			return &response{data: <-pass}, nil
 		default:
-			return NewError("Invalid call type %T", o)
+			return nil, &err{msg: fmt.Sprintf("Invalid call type %T", o)}
 		}
 	})
 	return NewClient(multiplexer.New(ctx, cr, cw, cw, mtu, nil), nil)
@@ -88,6 +97,18 @@
 	simpleRequest(t, c, "hello")
 }
 
+func TestError(t *testing.T) {
+	c := create(t)
+	data, got := c.Send(&err{msg: "not an RPC method"})
+	if data != nil {
+		t.Fatalf("Expected no data. Got: %v", data)
+	}
+	expected := &err{msg: "Invalid call type *rpc.err"}
+	if !reflect.DeepEqual(got, expected) {
+		t.Fatalf("Unexpected error from rpc. Expected: %v, got: %v", expected, got)
+	}
+}
+
 func TestInterleavedRpc(t *testing.T) {
 	c := create(t)
 	done := make(chan struct{})
diff --git a/rpc/server.go b/rpc/server.go
index 7afb6bf..c4102e4 100644
--- a/rpc/server.go
+++ b/rpc/server.go
@@ -29,7 +29,7 @@
 )
 
 // Handler is the signature for a function that handles incoming rpc calls.
-type Handler func(log.Context, interface{}) binary.Object
+type Handler func(log.Context, interface{}) (binary.Object, error)
 
 var lastTaskID = uint32(1)
 
@@ -51,30 +51,50 @@
 		d := cyclic.Decoder(vle.Reader(channel))
 		e := cyclic.Encoder(vle.Writer(w))
 
+		// Function used for encoding an error
+		encErr := func(err error) {
+			if rpcErr, ok := err.(Err); ok {
+				e.Object(rpcErr)
+			} else {
+				e.Object(&Error{Msg: err.Error()})
+			}
+		}
+
 		// Check the RPC header
 		var h [4]byte
-		if d.Data(h[:]); d.Error() != nil || h != header {
-			ctx.Fail(ErrInvalidHeader, "")
-			e.Object(ErrInvalidHeader)
-			return
+		d.Data(h[:])
+
+		switch h {
+		case headerV0:
+			// Legacy support for a single string error message type.
+			encErr = func(err error) { e.Object(&Error{Msg: err.Error()}) }
+		case headerV1:
+		default:
+			err := &ErrInvalidHeader{Header: h}
+			ctx.Fail(err, "")
+			encErr(err)
 		}
 
 		// Decode the call
 		val := d.Object()
-		if d.Error() != nil {
-			ctx.Error().Fail(d.Error(), "Decoding call")
-			e.Object(NewError("Failed to decode call. Reason: %v", d.Error()))
+		if err := d.Error(); err != nil {
+			ctx.Error().Fail(err, "Decoding call")
+			encErr(&ErrDecodingCall{Reason: err.Error()})
 			return
 		}
 
 		// Invoke the call
-		res := handler(ctx, val)
+		res, err := handler(ctx, val)
 
 		// Encode the call result
-		e.Object(res)
+		if err == nil {
+			e.Object(res) // Success
+		} else {
+			encErr(err) // Failure
+		}
+
 		if err := e.Error(); err != nil {
 			ctx.Error().V("value", val).Fail(err, "Encoding result")
-			e.Object(NewError("Failed to encode call result. Reason: %v", err))
 			return
 		}
 	})
diff --git a/rpc/test/rpc_test_server.go b/rpc/test/rpc_test_server.go
index 8953d3a..b53faca 100644
--- a/rpc/test/rpc_test_server.go
+++ b/rpc/test/rpc_test_server.go
@@ -21,83 +21,84 @@
 }
 
 func BindServer(ctx log.Context, r io.Reader, w io.Writer, c io.Closer, mtu int, server Server) {
-	rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object) {
-		ctx = ctx.Enter(fmt.Sprintf("%T", in))
+	rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object, err error) {
+		ty := fmt.Sprintf("%T", in)
+		ctx = ctx.Enter(ty)
 		defer func() {
 			if r := recover(); r == nil {
 				ctx.Info().Tag("rpc").WithValue("result", res).Log("")
 			} else {
-				err, ok := r.(error)
-				if !ok {
-					err = fmt.Errorf("%v", err)
+				var ok bool
+				if err, ok = r.(error); !ok {
+					err = fmt.Errorf("%v", r)
 				}
 				ctx.Fail(err, "")
-				res = rpc.NewError(fmt.Sprintf("Panic: '%v'. See gapis log for more information.", err))
+				err = &rpc.ErrPanic{Msg: err.Error()}
 			}
 		}()
 		switch call := in.(type) {
 		case *callAdd:
 			if res, err := server.Add(ctx, call.a, call.b); err == nil {
-				return &resultAdd{value: res}
+				return &resultAdd{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callEnumToString:
 			if res, err := server.EnumToString(ctx, call.e); err == nil {
-				return &resultEnumToString{value: res}
+				return &resultEnumToString{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetListNodeChain:
 			if res, err := server.GetListNodeChain(ctx); err == nil {
-				return &resultGetListNodeChain{value: res}
+				return &resultGetListNodeChain{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetListNodeChainArray:
 			if res, err := server.GetListNodeChainArray(ctx); err == nil {
-				return &resultGetListNodeChainArray{value: res}
+				return &resultGetListNodeChainArray{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetResource:
 			if res, err := server.GetResource(ctx); err == nil {
-				return &resultGetResource{value: res}
+				return &resultGetResource{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetSingleListNode:
 			if res, err := server.GetSingleListNode(ctx); err == nil {
-				return &resultGetSingleListNode{value: res}
+				return &resultGetSingleListNode{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetStruct:
 			if res, err := server.GetStruct(ctx); err == nil {
-				return &resultGetStruct{value: res}
+				return &resultGetStruct{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callResolveResource:
 			if res, err := server.ResolveResource(ctx, call.r); err == nil {
-				return &resultResolveResource{value: res}
+				return &resultResolveResource{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callSetStruct:
 			if err := server.SetStruct(ctx, call.s); err == nil {
-				return &resultSetStruct{}
+				return &resultSetStruct{}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callUseResource:
 			if err := server.UseResource(ctx, call.r); err == nil {
-				return &resultUseResource{}
+				return &resultUseResource{}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		default:
-			return rpc.NewError("Unexpected function: %T", call)
+			return nil, &rpc.ErrUnknownFunction{Function: ty}
 		}
 	})
 }
diff --git a/service/errors.go b/service/errors.go
new file mode 100644
index 0000000..06fbf3c
--- /dev/null
+++ b/service/errors.go
@@ -0,0 +1,15 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package service
diff --git a/service/path/errors.go b/service/path/errors.go
new file mode 100644
index 0000000..b6e9928
--- /dev/null
+++ b/service/path/errors.go
@@ -0,0 +1,32 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package path
+
+import (
+	"fmt"
+
+	"android.googlesource.com/platform/tools/gpu/binary"
+)
+
+// ErrNotFollowable is the error returned when a path cannot be followed
+// using the Service.Follow RPC function.
+type ErrNotFollowable struct {
+	binary.Generate `implements:"rpc.Err" java:"ErrPathNotFollowable"`
+	Path            Path
+}
+
+func (e *ErrNotFollowable) Error() string {
+	return fmt.Sprintf("Path '%v' is not followable", e.Path)
+}
diff --git a/service/path/path_binary.go b/service/path/path_binary.go
index 7022dd4..1d79d0a 100644
--- a/service/path/path_binary.go
+++ b/service/path/path_binary.go
@@ -9,6 +9,7 @@
 	"android.googlesource.com/platform/tools/gpu/binary"
 	"android.googlesource.com/platform/tools/gpu/binary/registry"
 	"android.googlesource.com/platform/tools/gpu/binary/schema"
+	"android.googlesource.com/platform/tools/gpu/rpc"
 )
 
 // Make sure schema init() runs first
@@ -22,6 +23,7 @@
 	ixǁAtom
 	ixǁBlob
 	ixǁDevice
+	ixǁErrNotFollowable
 	ixǁField
 	ixǁHierarchy
 	ixǁImageInfo
@@ -36,7 +38,7 @@
 	ixǁTimingInfo
 )
 
-var entities [19]binary.Entity
+var entities [20]binary.Entity
 
 var Namespace = registry.NewNamespace()
 
@@ -49,6 +51,7 @@
 	Namespace.AddClassOf((*Atom)(nil))
 	Namespace.AddClassOf((*Blob)(nil))
 	Namespace.AddClassOf((*Device)(nil))
+	Namespace.AddClassOf((*ErrNotFollowable)(nil))
 	Namespace.AddClassOf((*Field)(nil))
 	Namespace.AddClassOf((*Hierarchy)(nil))
 	Namespace.AddClassOf((*ImageInfo)(nil))
@@ -271,6 +274,31 @@
 	return &entities[ixǁDevice]
 }
 
+var _ rpc.Err = (*ErrNotFollowable)(nil) // Interface compliance check.
+type binaryClassErrNotFollowable struct{}
+
+func (*ErrNotFollowable) Class() binary.Class {
+	return (*binaryClassErrNotFollowable)(nil)
+}
+func doEncodeErrNotFollowable(e binary.Encoder, o *ErrNotFollowable) {
+	e.Object(o.Path)
+}
+func doDecodeErrNotFollowable(d binary.Decoder, o *ErrNotFollowable) {
+	o.Path = PathCast(d.Object())
+}
+func (*binaryClassErrNotFollowable) Encode(e binary.Encoder, obj binary.Object) {
+	doEncodeErrNotFollowable(e, obj.(*ErrNotFollowable))
+}
+func (*binaryClassErrNotFollowable) New() binary.Object {
+	return &ErrNotFollowable{}
+}
+func (*binaryClassErrNotFollowable) DecodeTo(d binary.Decoder, obj binary.Object) {
+	doDecodeErrNotFollowable(d, obj.(*ErrNotFollowable))
+}
+func (c *binaryClassErrNotFollowable) Schema() *binary.Entity {
+	return &entities[ixǁErrNotFollowable]
+}
+
 type binaryClassField struct{}
 
 func (*Field) Class() binary.Class {
diff --git a/service/service_server.go b/service/service_server.go
index cb2c782..a2696b8 100644
--- a/service/service_server.go
+++ b/service/service_server.go
@@ -22,107 +22,108 @@
 }
 
 func BindServer(ctx log.Context, r io.Reader, w io.Writer, c io.Closer, mtu int, server Server) {
-	rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object) {
-		ctx = ctx.Enter(fmt.Sprintf("%T", in))
+	rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object, err error) {
+		ty := fmt.Sprintf("%T", in)
+		ctx = ctx.Enter(ty)
 		defer func() {
 			if r := recover(); r == nil {
 				ctx.Info().Tag("rpc").WithValue("result", res).Log("")
 			} else {
-				err, ok := r.(error)
-				if !ok {
-					err = fmt.Errorf("%v", err)
+				var ok bool
+				if err, ok = r.(error); !ok {
+					err = fmt.Errorf("%v", r)
 				}
 				ctx.Fail(err, "")
-				res = rpc.NewError(fmt.Sprintf("Panic: '%v'. See gapis log for more information.", err))
+				err = &rpc.ErrPanic{Msg: err.Error()}
 			}
 		}()
 		switch call := in.(type) {
 		case *callBeginCPUProfile:
 			if err := server.BeginCPUProfile(ctx); err == nil {
-				return &resultBeginCPUProfile{}
+				return &resultBeginCPUProfile{}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callEndCPUProfile:
 			if res, err := server.EndCPUProfile(ctx); err == nil {
-				return &resultEndCPUProfile{value: res}
+				return &resultEndCPUProfile{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callFollow:
 			if res, err := server.Follow(ctx, call.p); err == nil {
-				return &resultFollow{value: res}
+				return &resultFollow{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGet:
 			if res, err := server.Get(ctx, call.p); err == nil {
-				return &resultGet{value: res}
+				return &resultGet{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetCaptures:
 			if res, err := server.GetCaptures(ctx); err == nil {
-				return &resultGetCaptures{value: res}
+				return &resultGetCaptures{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetDevices:
 			if res, err := server.GetDevices(ctx); err == nil {
-				return &resultGetDevices{value: res}
+				return &resultGetDevices{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetFeatures:
 			if res, err := server.GetFeatures(ctx); err == nil {
-				return &resultGetFeatures{value: res}
+				return &resultGetFeatures{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetFramebufferColor:
 			if res, err := server.GetFramebufferColor(ctx, call.device, call.after, call.settings); err == nil {
-				return &resultGetFramebufferColor{value: res}
+				return &resultGetFramebufferColor{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetFramebufferDepth:
 			if res, err := server.GetFramebufferDepth(ctx, call.device, call.after); err == nil {
-				return &resultGetFramebufferDepth{value: res}
+				return &resultGetFramebufferDepth{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetSchema:
 			if res, err := server.GetSchema(ctx); err == nil {
-				return &resultGetSchema{value: res}
+				return &resultGetSchema{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callGetTimingInfo:
 			if res, err := server.GetTimingInfo(ctx, call.device, call.capture, call.flags); err == nil {
-				return &resultGetTimingInfo{value: res}
+				return &resultGetTimingInfo{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callImportCapture:
 			if res, err := server.ImportCapture(ctx, call.name, call.Data); err == nil {
-				return &resultImportCapture{value: res}
+				return &resultImportCapture{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callLoadCapture:
 			if res, err := server.LoadCapture(ctx, call.path); err == nil {
-				return &resultLoadCapture{value: res}
+				return &resultLoadCapture{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		case *callSet:
 			if res, err := server.Set(ctx, call.p, call.v); err == nil {
-				return &resultSet{value: res}
+				return &resultSet{value: res}, nil
 			} else {
-				return rpc.NewError(err.Error())
+				return nil, err
 			}
 		default:
-			return rpc.NewError("Unexpected function: %T", call)
+			return nil, &rpc.ErrUnknownFunction{Function: ty}
 		}
 	})
 }
diff --git a/signatures.txt b/signatures.txt
index bc8ad50..2acb4da 100644
--- a/signatures.txt
+++ b/signatures.txt
@@ -3788,6 +3788,9 @@
 Device : pod : full 39 compact 19
 path.Device{[20]Uint8}
 
+ErrNotFollowable : entity : full 39 compact 27
+path.ErrNotFollowable{?}
+
 Field : entity : full 43 compact 17
 path.Field{?,String}
 
@@ -3830,7 +3833,19 @@
 ResourceInfo : pod : full 52 compact 28
 protocol.ResourceInfo{String,Uint32}
 
-Error : pod : full 32 compact 15
+ErrDecodingCall : pod : full 41 compact 25
+rpc.ErrDecodingCall{String}
+
+ErrInvalidHeader : pod : full 43 compact 28
+rpc.ErrInvalidHeader{[4]Uint8}
+
+ErrPanic : pod : full 31 compact 18
+rpc.ErrPanic{String}
+
+ErrUnknownFunction : pod : full 46 compact 28
+rpc.ErrUnknownFunction{String}
+
+Error : pod : full 28 compact 15
 rpc.Error{String}
 
 AtomRangeTimer : pod : full 91 compact 30
@@ -4434,12 +4449,12 @@
 test.resultUseResource{}
 
 Schema stats:
-Count: 1478
-Full: 153054
-Total: 152157
+Count: 1483
+Full: 153250
+Total: 152353
 Average: 102
 Largest: 4837
-Compact: 47062
-Total: 46172
+Compact: 47188
+Total: 46298
 Average: 31
 Largest: 174
diff --git a/tools/codergen/template/embed.go b/tools/codergen/template/embed.go
index ba8cd75..35fc16c 100644
--- a/tools/codergen/template/embed.go
+++ b/tools/codergen/template/embed.go
@@ -1029,18 +1029,19 @@
   «}¶

   func BindServer(ctx log.Context, r io.Reader, w io.Writer, c io.Closer, mtu int, server Server) {Ȧ
-    rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object) {Ȧ
-      ctx = ctx.Enter(fmt.Sprintf("%T", in))¶
+    rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object, err error) {Ȧ
+      ty := fmt.Sprintf("%T", in)¶
+      ctx = ctx.Enter(ty)¶
       defer func() {Ȧ
         if r := recover(); r == nil {Ȧ
           ctx.Info().Tag("rpc").WithValue("result", res).Log("")¶
         «} else {»¶
-          err, ok := r.(error)¶
-          if !ok {Ȧ
-            err = fmt.Errorf("%v", err)¶
+          var ok bool¶
+          if err, ok = r.(error); !ok {Ȧ
+            err = fmt.Errorf("%v", r)¶
           «}¶
           ctx.Fail(err, "")¶
-          res = rpc.NewError(fmt.Sprintf("Panic: '%v'. See gapis log for more information.", err))¶
+          err = &rpc.ErrPanic{Msg: err.Error()}¶
         «}¶
       «}()¶
       switch call := in.(type) {¶
@@ -1054,15 +1055,15 @@
                 ); err == nil {Ȧ
                 return &{{.Result.Name}}{
                   {{if .Result.Type}}value: res{{end}}
-                }¶
+                }, nil¶
               «} else {»¶
-                return rpc.NewError(err.Error())¶
+                return nil, err¶
               «}¶
             «
           {{end}}
         {{end}}
           default:Ȧ
-            return rpc.NewError("Unexpected function: %T", call)¶
+            return nil, &rpc.ErrUnknownFunction{Function: ty}¶
           «

     «})¶
diff --git a/tools/codergen/template/go_server.tmpl b/tools/codergen/template/go_server.tmpl
index 163aa65..c620ea7 100644
--- a/tools/codergen/template/go_server.tmpl
+++ b/tools/codergen/template/go_server.tmpl
@@ -29,18 +29,19 @@
   «}¶

   func BindServer(ctx log.Context, r io.Reader, w io.Writer, c io.Closer, mtu int, server Server) {Ȧ
-    rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object) {Ȧ
-      ctx = ctx.Enter(fmt.Sprintf("%T", in))¶
+    rpc.Serve(ctx, r, w, c, mtu, func(ctx log.Context, in interface{}) (res binary.Object, err error) {Ȧ
+      ty := fmt.Sprintf("%T", in)¶
+      ctx = ctx.Enter(ty)¶
       defer func() {Ȧ
         if r := recover(); r == nil {Ȧ
           ctx.Info().Tag("rpc").WithValue("result", res).Log("")¶
         «} else {»¶
-          err, ok := r.(error)¶
-          if !ok {Ȧ
-            err = fmt.Errorf("%v", err)¶
+          var ok bool¶
+          if err, ok = r.(error); !ok {Ȧ
+            err = fmt.Errorf("%v", r)¶
           «}¶
           ctx.Fail(err, "")¶
-          res = rpc.NewError(fmt.Sprintf("Panic: '%v'. See gapis log for more information.", err))¶
+          err = &rpc.ErrPanic{Msg: err.Error()}¶
         «}¶
       «}()¶
       switch call := in.(type) {¶
@@ -54,15 +55,15 @@
                 ); err == nil {Ȧ
                 return &{{.Result.Name}}{
                   {{if .Result.Type}}value: res{{end}}
-                }¶
+                }, nil¶
               «} else {»¶
-                return rpc.NewError(err.Error())¶
+                return nil, err¶
               «}¶
             «
           {{end}}
         {{end}}
           default:Ȧ
-            return rpc.NewError("Unexpected function: %T", call)¶
+            return nil, &rpc.ErrUnknownFunction{Function: ty}¶
           «

     «})¶