Skip to content

Commit

Permalink
lib: recover panics within libraries and convert to informative errors
Browse files Browse the repository at this point in the history
Wrap extensions that could panic with a recover to collect the latest
mito/lib line in a stack trace and decorate the error with the failing
AST node's ID. Libs that do not have a reasonable potential to panic are
not included in this change: crypto, send, strings, time and try.
  • Loading branch information
efd6 committed Jul 18, 2024
1 parent 0e4b822 commit eb68e7e
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 72 deletions.
50 changes: 25 additions & 25 deletions lib/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,25 +291,25 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_collate_string",
[]*cel.Type{listDyn, cel.StringType},
listDyn,
cel.BinaryBinding(collateFields),
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"list_collate_list_string",
[]*cel.Type{listDyn, listString},
listDyn,
cel.BinaryBinding(collateFields),
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"map_collate_string",
[]*cel.Type{mapStringDyn, cel.StringType},
listDyn,
cel.BinaryBinding(collateFields),
cel.BinaryBinding(catch(collateFields)),
),
cel.MemberOverload(
"map_collate_list_string",
[]*cel.Type{mapStringDyn, listString},
listDyn,
cel.BinaryBinding(collateFields),
cel.BinaryBinding(catch(collateFields)),
),
),

Expand All @@ -318,25 +318,25 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_drop_string",
[]*cel.Type{listV, cel.StringType},
listV,
cel.BinaryBinding(dropFields),
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"list_drop_list_string",
[]*cel.Type{listV, listString},
listV,
cel.BinaryBinding(dropFields),
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"map_drop_string",
[]*cel.Type{mapKV, cel.StringType},
mapKV,
cel.BinaryBinding(dropFields),
cel.BinaryBinding(catch(dropFields)),
),
cel.MemberOverload(
"map_drop_list_string",
[]*cel.Type{mapKV, listString},
mapKV,
cel.BinaryBinding(dropFields),
cel.BinaryBinding(catch(dropFields)),
),
),

Expand All @@ -345,13 +345,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_drop_empty",
[]*cel.Type{listV},
listV,
cel.UnaryBinding(dropEmpty),
cel.UnaryBinding(catch(dropEmpty)),
),
cel.MemberOverload(
"map_drop_empty",
[]*cel.Type{mapKV},
mapKV,
cel.UnaryBinding(dropEmpty),
cel.UnaryBinding(catch(dropEmpty)),
),
),

Expand All @@ -360,7 +360,7 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_flatten",
[]*cel.Type{listV},
listV,
cel.UnaryBinding(flatten),
cel.UnaryBinding(catch(flatten)),
),
),

Expand All @@ -369,13 +369,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"map_keys",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(mapKeys),
cel.UnaryBinding(catch(mapKeys)),
),
cel.Overload(
"keys_map",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(mapKeys),
cel.UnaryBinding(catch(mapKeys)),
),
),

Expand All @@ -384,13 +384,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_max",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(max),
cel.UnaryBinding(catch(max)),
),
cel.Overload(
"max_list",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(max),
cel.UnaryBinding(catch(max)),
),
),

Expand All @@ -399,13 +399,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_min",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(min),
cel.UnaryBinding(catch(min)),
),
cel.Overload(
"min_list",
[]*cel.Type{listV},
typeV,
cel.UnaryBinding(min),
cel.UnaryBinding(catch(min)),
),
),

Expand All @@ -414,7 +414,7 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"tail_list",
[]*cel.Type{listV},
listV,
cel.UnaryBinding(tail),
cel.UnaryBinding(catch(tail)),
),
),

Expand All @@ -423,13 +423,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"map_values",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(mapValues),
cel.UnaryBinding(catch(mapValues)),
),
cel.Overload(
"values_map",
[]*cel.Type{mapKV},
listK,
cel.UnaryBinding(mapValues),
cel.UnaryBinding(catch(mapValues)),
),
),

Expand All @@ -438,7 +438,7 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"map_with_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(withAll),
cel.BinaryBinding(catch(withAll)),
),
),

Expand All @@ -447,7 +447,7 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"map_with_update_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(withUpdate),
cel.BinaryBinding(catch(withUpdate)),
),
),

Expand All @@ -456,7 +456,7 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"map_with_replace_map",
[]*cel.Type{mapKV, mapKV},
mapKV,
cel.BinaryBinding(withReplace),
cel.BinaryBinding(catch(withReplace)),
),
),

Expand All @@ -465,13 +465,13 @@ func (collectionsLib) CompileOptions() []cel.EnvOption {
"list_zip",
[]*cel.Type{listK, listV},
mapKV,
cel.BinaryBinding(zipLists),
cel.BinaryBinding(catch(zipLists)),
),
cel.Overload(
"zip_list",
[]*cel.Type{listK, listV},
mapKV,
cel.BinaryBinding(zipLists),
cel.BinaryBinding(catch(zipLists)),
),
),
}
Expand Down
2 changes: 1 addition & 1 deletion lib/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (l debug) CompileOptions() []cel.EnvOption {
"debug_string_dyn",
[]*cel.Type{cel.StringType, cel.DynType},
cel.DynType,
cel.BinaryBinding(l.logDebug),
cel.BinaryBinding(catch(l.logDebug)),
cel.OverloadIsNonStrict(),
),
),
Expand Down
64 changes: 64 additions & 0 deletions lib/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ package lib

import (
"fmt"
"path"
"runtime"
"strings"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/common"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)

// DecoratedError implements error source location rendering.
Expand Down Expand Up @@ -82,3 +87,62 @@ func nodeID(err error) (id int64, ok bool) {
}
}
}

type (
unop = func(value ref.Val) ref.Val
binop = func(lhs ref.Val, rhs ref.Val) ref.Val
varop = func(values ...ref.Val) ref.Val

bindings interface {
~unop | ~binop | ~varop
}
)

func catch[B bindings](binding B) B {
switch binding := any(binding).(type) {
case unop:
return any(unop(func(arg ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(arg)
})).(B)
case binop:
return any(binop(func(arg0, arg1 ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(arg0, arg1)
})).(B)
case varop:
return any(varop(func(args ...ref.Val) (ret ref.Val) {
defer handlePanic(&ret)
return binding(args...)
})).(B)
default:
panic("unreachable")
}
}

func handlePanic(ret *ref.Val) {
switch r := recover().(type) {
case nil:
return
default:
// We'll only try 64 stack frames deep. There are a few recursive
// functions in extensions, but panic in those functions are unlikely
// or are already explicitly recovered as part of normal operation.
pc := make([]uintptr, 64)
n := runtime.Callers(2, pc)
cf := runtime.CallersFrames(pc[:n])
for {
f, more := cf.Next()
if !more {
break
}
file := f.File
if strings.Contains(file, "mito/lib") {
_, file, _ := strings.Cut(file, "mito/")
*ret = types.NewErr("%s: %s %s:%d", r, path.Base(f.Function), file, f.Line)
return
}
}
*ret = types.NewErr("%s", r)
}
}
6 changes: 3 additions & 3 deletions lib/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,21 @@ func (l fileLib) CompileOptions() []cel.EnvOption {
"dir_string",
[]*cel.Type{cel.StringType},
cel.ListType(mapStringDyn),
cel.UnaryBinding(readDir),
cel.UnaryBinding(catch(readDir)),
),
),
cel.Function("file",
cel.Overload(
"file_string",
[]*cel.Type{cel.StringType},
cel.BytesType,
cel.UnaryBinding(readFile),
cel.UnaryBinding(catch(readFile)),
),
cel.Overload(
"file_string_string",
[]*cel.Type{cel.StringType, cel.StringType},
cel.DynType,
cel.BinaryBinding(l.readMIMEFile),
cel.BinaryBinding(catch(l.readMIMEFile)),
),
),
}
Expand Down
Loading

0 comments on commit eb68e7e

Please sign in to comment.