2017-01-28 22:39:52 +08:00
|
|
|
package eval
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2017-01-29 10:29:03 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
2017-08-31 02:52:27 +08:00
|
|
|
"unsafe"
|
2017-01-28 22:39:52 +08:00
|
|
|
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
|
|
"github.com/elves/elvish/pkg/parse"
|
|
|
|
"github.com/elves/elvish/pkg/util"
|
2017-08-31 02:52:27 +08:00
|
|
|
"github.com/xiaq/persistent/hash"
|
2017-01-28 22:39:52 +08:00
|
|
|
)
|
|
|
|
|
2017-01-29 10:29:03 +08:00
|
|
|
// Exception represents an elvish exception. It is both a Value accessible to
|
|
|
|
// elvishscript, and the type of error returned by public facing evaluation
|
|
|
|
// methods like (*Evaler)PEval.
|
2017-01-28 22:39:52 +08:00
|
|
|
type Exception struct {
|
|
|
|
Cause error
|
2018-03-01 09:19:33 +08:00
|
|
|
Traceback *stackTrace
|
2017-01-28 22:39:52 +08:00
|
|
|
}
|
|
|
|
|
2018-10-09 21:21:26 +08:00
|
|
|
// Cause returns the Cause field if err is an *Exception. Otherwise it returns
|
|
|
|
// err itself.
|
|
|
|
func Cause(err error) error {
|
|
|
|
if exc, ok := err.(*Exception); ok {
|
|
|
|
return exc.Cause
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-05-22 06:57:57 +08:00
|
|
|
// OK is a pointer to the zero value of Exception, representing the absence of
|
2017-01-29 10:29:03 +08:00
|
|
|
// exception.
|
|
|
|
var OK = &Exception{}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Error returns the message of the cause of the exception.
|
2017-01-28 22:39:52 +08:00
|
|
|
func (exc *Exception) Error() string {
|
|
|
|
return exc.Cause.Error()
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// PPrint pretty-prints the exception.
|
2018-10-13 23:51:00 +08:00
|
|
|
func (exc *Exception) PPrint(indent string) string {
|
2017-01-28 22:39:52 +08:00
|
|
|
buf := new(bytes.Buffer)
|
2017-12-19 07:13:41 +08:00
|
|
|
|
|
|
|
var causeDescription string
|
2018-10-13 23:51:00 +08:00
|
|
|
if pprinter, ok := exc.Cause.(util.PPrinter); ok {
|
|
|
|
causeDescription = pprinter.PPrint(indent)
|
2017-01-29 10:29:03 +08:00
|
|
|
} else {
|
2017-12-19 07:13:41 +08:00
|
|
|
causeDescription = "\033[31;1m" + exc.Cause.Error() + "\033[m"
|
2017-01-29 10:29:03 +08:00
|
|
|
}
|
2017-12-19 07:13:41 +08:00
|
|
|
fmt.Fprintf(buf, "Exception: %s\n", causeDescription)
|
2017-01-28 22:39:52 +08:00
|
|
|
|
2018-03-01 09:19:33 +08:00
|
|
|
if exc.Traceback.next == nil {
|
2018-10-13 23:51:00 +08:00
|
|
|
buf.WriteString(exc.Traceback.entry.PPrintCompact(indent))
|
2017-12-19 07:13:41 +08:00
|
|
|
} else {
|
|
|
|
buf.WriteString(indent + "Traceback:")
|
2018-03-01 09:19:33 +08:00
|
|
|
for tb := exc.Traceback; tb != nil; tb = tb.next {
|
2017-12-19 07:13:41 +08:00
|
|
|
buf.WriteString("\n" + indent + " ")
|
2018-10-13 23:51:00 +08:00
|
|
|
buf.WriteString(tb.entry.PPrint(indent + " "))
|
2017-12-19 07:13:41 +08:00
|
|
|
}
|
2017-01-28 22:39:52 +08:00
|
|
|
}
|
|
|
|
|
2017-01-30 02:35:18 +08:00
|
|
|
if pipeExcs, ok := exc.Cause.(PipelineError); ok {
|
|
|
|
buf.WriteString("\n" + indent + "Caused by:")
|
|
|
|
for _, e := range pipeExcs.Errors {
|
2017-06-20 04:57:51 +08:00
|
|
|
if e == OK {
|
|
|
|
continue
|
|
|
|
}
|
2018-10-13 23:51:00 +08:00
|
|
|
buf.WriteString("\n" + indent + " " + e.PPrint(indent+" "))
|
2017-01-30 02:35:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-28 22:39:52 +08:00
|
|
|
return buf.String()
|
|
|
|
}
|
2017-01-29 10:29:03 +08:00
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Kind returns "exception".
|
2017-01-29 10:29:03 +08:00
|
|
|
func (exc *Exception) Kind() string {
|
|
|
|
return "exception"
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Repr returns a representation of the exception. It is lossy in that it does
|
|
|
|
// not preserve the stacktrace.
|
2017-01-29 10:29:03 +08:00
|
|
|
func (exc *Exception) Repr(indent int) string {
|
|
|
|
if exc.Cause == nil {
|
|
|
|
return "$ok"
|
|
|
|
}
|
2018-02-15 17:14:05 +08:00
|
|
|
if r, ok := exc.Cause.(vals.Reprer); ok {
|
2017-01-29 10:29:03 +08:00
|
|
|
return r.Repr(indent)
|
|
|
|
}
|
2017-03-07 09:07:14 +08:00
|
|
|
return "?(fail " + parse.Quote(exc.Cause.Error()) + ")"
|
2017-01-29 10:29:03 +08:00
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Equal compares by address.
|
2017-08-31 01:47:50 +08:00
|
|
|
func (exc *Exception) Equal(rhs interface{}) bool {
|
2017-07-14 08:15:14 +08:00
|
|
|
return exc == rhs
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Hash returns the hash of the address.
|
2017-08-31 02:52:27 +08:00
|
|
|
func (exc *Exception) Hash() uint32 {
|
|
|
|
return hash.Pointer(unsafe.Pointer(exc))
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Bool returns whether this exception has a nil cause; that is, it is $ok.
|
2017-01-29 10:29:03 +08:00
|
|
|
func (exc *Exception) Bool() bool {
|
|
|
|
return exc.Cause == nil
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Index supports introspection of the exception. Currently the only supported
|
|
|
|
// key is "cause".
|
2018-03-08 21:15:18 +08:00
|
|
|
func (exc *Exception) Index(k interface{}) (interface{}, bool) {
|
|
|
|
// TODO: Access to Traceback
|
|
|
|
switch k {
|
|
|
|
case "cause":
|
|
|
|
return exc.Cause, true
|
|
|
|
default:
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// IterateKeys calls f with all the valid keys that can be used in Index.
|
2018-03-08 21:15:18 +08:00
|
|
|
func (exc *Exception) IterateKeys(f func(interface{}) bool) {
|
2018-10-11 00:25:24 +08:00
|
|
|
util.Feed(f, "cause")
|
2018-03-08 21:15:18 +08:00
|
|
|
}
|
|
|
|
|
2017-01-29 10:29:03 +08:00
|
|
|
// PipelineError represents the errors of pipelines, in which multiple commands
|
|
|
|
// may error.
|
|
|
|
type PipelineError struct {
|
|
|
|
Errors []*Exception
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Repr returns a representation of the pipeline error, using the multi-error builtin.
|
2017-01-29 10:29:03 +08:00
|
|
|
func (pe PipelineError) Repr(indent int) string {
|
|
|
|
// TODO Make a more generalized ListReprBuilder and use it here.
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
b.WriteString("?(multi-error")
|
|
|
|
elemIndent := indent + len("?(multi-error ")
|
|
|
|
for _, e := range pe.Errors {
|
|
|
|
if indent > 0 {
|
|
|
|
b.WriteString("\n" + strings.Repeat(" ", elemIndent))
|
|
|
|
} else {
|
|
|
|
b.WriteString(" ")
|
|
|
|
}
|
|
|
|
b.WriteString(e.Repr(elemIndent))
|
|
|
|
}
|
|
|
|
b.WriteString(")")
|
|
|
|
return b.String()
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Error returns a plain text representation of the pipeline error.
|
2017-01-29 10:29:03 +08:00
|
|
|
func (pe PipelineError) Error() string {
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
b.WriteString("(")
|
|
|
|
for i, e := range pe.Errors {
|
|
|
|
if i > 0 {
|
|
|
|
b.WriteString(" | ")
|
|
|
|
}
|
|
|
|
if e == nil || e.Cause == nil {
|
|
|
|
b.WriteString("<nil>")
|
|
|
|
} else {
|
|
|
|
b.WriteString(e.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.WriteString(")")
|
|
|
|
return b.String()
|
|
|
|
}
|
|
|
|
|
2017-01-30 01:56:13 +08:00
|
|
|
// ComposeExceptionsFromPipeline takes a slice of Exception pointers and
|
|
|
|
// composes a suitable error. If all elements of the slice are either nil or OK,
|
|
|
|
// a nil is returned. If there is exactly non-nil non-OK Exception, it is
|
|
|
|
// returned. Otherwise, a PipelineError built from the slice is returned, with
|
|
|
|
// nil items turned into OK's for easier access from elvishscript.
|
|
|
|
func ComposeExceptionsFromPipeline(excs []*Exception) error {
|
|
|
|
newexcs := make([]*Exception, len(excs))
|
|
|
|
notOK, lastNotOK := 0, 0
|
|
|
|
for i, e := range excs {
|
|
|
|
if e == nil {
|
|
|
|
newexcs[i] = OK
|
|
|
|
} else {
|
|
|
|
newexcs[i] = e
|
|
|
|
if e.Cause != nil {
|
|
|
|
notOK++
|
|
|
|
lastNotOK = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch notOK {
|
|
|
|
case 0:
|
|
|
|
return nil
|
|
|
|
case 1:
|
|
|
|
return newexcs[lastNotOK]
|
|
|
|
default:
|
|
|
|
return PipelineError{newexcs}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 10:29:03 +08:00
|
|
|
// Flow is a special type of error used for control flows.
|
|
|
|
type Flow uint
|
|
|
|
|
|
|
|
// Control flows.
|
|
|
|
const (
|
|
|
|
Return Flow = iota
|
|
|
|
Break
|
|
|
|
Continue
|
|
|
|
)
|
|
|
|
|
|
|
|
var flowNames = [...]string{
|
|
|
|
"return", "break", "continue",
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// Repr returns a representation of the flow "error".
|
2017-01-29 10:29:03 +08:00
|
|
|
func (f Flow) Repr(int) string {
|
|
|
|
return "?(" + f.Error() + ")"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f Flow) Error() string {
|
|
|
|
if f >= Flow(len(flowNames)) {
|
|
|
|
return fmt.Sprintf("!(BAD FLOW: %v)", f)
|
|
|
|
}
|
|
|
|
return flowNames[f]
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// PPrint pretty-prints the flow "error".
|
2018-10-13 23:51:00 +08:00
|
|
|
func (f Flow) PPrint(string) string {
|
2017-01-29 10:29:03 +08:00
|
|
|
return "\033[33;1m" + f.Error() + "\033[m"
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExternalCmdExit contains the exit status of external commands. If the
|
|
|
|
// command was stopped rather than terminated, the Pid field contains the pid
|
|
|
|
// of the process.
|
|
|
|
type ExternalCmdExit struct {
|
|
|
|
syscall.WaitStatus
|
|
|
|
CmdName string
|
|
|
|
Pid int
|
|
|
|
}
|
|
|
|
|
2019-04-19 05:15:34 +08:00
|
|
|
// NewExternalCmdExit constructs an error for representing a non-zero exit from
|
|
|
|
// an external command.
|
2017-01-29 10:29:03 +08:00
|
|
|
func NewExternalCmdExit(name string, ws syscall.WaitStatus, pid int) error {
|
|
|
|
if ws.Exited() && ws.ExitStatus() == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if !ws.Stopped() {
|
|
|
|
pid = 0
|
|
|
|
}
|
|
|
|
return ExternalCmdExit{ws, name, pid}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (exit ExternalCmdExit) Error() string {
|
|
|
|
ws := exit.WaitStatus
|
|
|
|
quotedName := parse.Quote(exit.CmdName)
|
|
|
|
switch {
|
|
|
|
case ws.Exited():
|
|
|
|
return quotedName + " exited with " + strconv.Itoa(ws.ExitStatus())
|
|
|
|
case ws.Signaled():
|
2017-12-19 07:13:41 +08:00
|
|
|
causeDescription := quotedName + " killed by signal " + ws.Signal().String()
|
2017-01-29 10:29:03 +08:00
|
|
|
if ws.CoreDump() {
|
2017-12-19 07:13:41 +08:00
|
|
|
causeDescription += " (core dumped)"
|
2017-01-29 10:29:03 +08:00
|
|
|
}
|
2017-12-19 07:13:41 +08:00
|
|
|
return causeDescription
|
2017-01-29 10:29:03 +08:00
|
|
|
case ws.Stopped():
|
2017-12-19 07:13:41 +08:00
|
|
|
causeDescription := quotedName + " stopped by signal " + fmt.Sprintf("%s (pid=%d)", ws.StopSignal(), exit.Pid)
|
2017-01-29 10:29:03 +08:00
|
|
|
trap := ws.TrapCause()
|
|
|
|
if trap != -1 {
|
2017-12-19 07:13:41 +08:00
|
|
|
causeDescription += fmt.Sprintf(" (trapped %v)", trap)
|
2017-01-29 10:29:03 +08:00
|
|
|
}
|
2017-12-19 07:13:41 +08:00
|
|
|
return causeDescription
|
2017-01-29 10:29:03 +08:00
|
|
|
default:
|
|
|
|
return fmt.Sprint(quotedName, " has unknown WaitStatus ", ws)
|
|
|
|
}
|
|
|
|
}
|