Skip to content

Commit

Permalink
Merge pull request #2683 from onflow/supun/handle-external-errors
Browse files Browse the repository at this point in the history
Wrap host env errors with external errors
  • Loading branch information
SupunS authored Aug 1, 2023
2 parents 181924e + fe2ba63 commit 5ea8284
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 32 deletions.
1 change: 1 addition & 0 deletions go.cap
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ github.com/stretchr/testify/require (network)
github.com/texttheater/golang-levenshtein/levenshtein (file)
github.com/zeebo/blake3/internal/consts (file)
golang.org/x/xerrors (runtime)
github.com/onflow/cadence/runtime/interpreter (runtime)
20 changes: 19 additions & 1 deletion runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ func (e *interpreterEnvironment) newLocationHandler() sema.LocationHandlerFunc {
errors.WrapPanic(func() {
res, err = e.runtimeInterface.ResolveLocation(identifiers, location)
})

if err != nil {
err = interpreter.WrappedExternalError(err)
}
return
}
}
Expand Down Expand Up @@ -560,6 +564,11 @@ func (e *interpreterEnvironment) getProgram(
if panicErr != nil {
return nil, panicErr
}

if err != nil {
err = interpreter.WrappedExternalError(err)
}

return
})
})
Expand All @@ -577,6 +586,11 @@ func (e *interpreterEnvironment) getCode(location common.Location) (code []byte,
code, err = e.runtimeInterface.GetCode(location)
})
}

if err != nil {
err = interpreter.WrappedExternalError(err)
}

return
}

Expand Down Expand Up @@ -745,6 +759,10 @@ func (e *interpreterEnvironment) newUUIDHandler() interpreter.UUIDHandlerFunc {
errors.WrapPanic(func() {
uuid, err = e.runtimeInterface.GenerateUUID()
})

if err != nil {
err = interpreter.WrappedExternalError(err)
}
return
}
}
Expand Down Expand Up @@ -941,7 +959,7 @@ func (e *interpreterEnvironment) newOnMeterComputation() interpreter.OnMeterComp
err = e.runtimeInterface.MeterComputation(compKind, intensity)
})
if err != nil {
panic(err)
panic(interpreter.WrappedExternalError(err))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion runtime/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ func emitEventFields(
err = emitEvent(exportedEvent)
})
if err != nil {
panic(err)
panic(interpreter.WrappedExternalError(err))
}
}
20 changes: 20 additions & 0 deletions runtime/interpreter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package interpreter

import (
"fmt"
"runtime"
"strings"

"github.com/onflow/cadence/runtime/ast"
Expand Down Expand Up @@ -988,3 +989,22 @@ func (RecursiveTransferError) IsUserError() {}
func (RecursiveTransferError) Error() string {
return "recursive transfer of value"
}

func WrappedExternalError(err error) error {
switch err := err.(type) {
case
// If the error is a go-runtime error, don't wrap.
// These are crashers.
runtime.Error,

// If the error is already a cadence error, then avoid redundant wrapping.
errors.InternalError,
errors.UserError,
errors.ExternalError,
Error:
return err

default:
return errors.NewExternalError(err)
}
}
1 change: 1 addition & 0 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ func getWrappedError(recovered any, location Location, codesAndPrograms codesAnd
return newError(err, location, codesAndPrograms)
}
}

func (r *interpreterRuntime) NewScriptExecutor(
script Script,
context Context,
Expand Down
160 changes: 160 additions & 0 deletions runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9000,3 +9000,163 @@ func TestRuntimeReturnDestroyedOptional(t *testing.T) {

require.ErrorAs(t, err, &interpreter.DestroyedResourceError{})
}

func TestRuntimeComputationMeteringError(t *testing.T) {

t.Parallel()

runtime := newTestInterpreterRuntime()

t.Run("regular error returned", func(t *testing.T) {
t.Parallel()

script := []byte(`
access(all) fun foo() {}
pub fun main() {
foo()
}
`)

runtimeInterface := &testRuntimeInterface{
storage: newTestLedger(nil, nil),
meterComputation: func(compKind common.ComputationKind, intensity uint) error {
return fmt.Errorf("computation limit exceeded")
},
}

_, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: common.ScriptLocation{},
},
)

require.Error(t, err)

// Returned error MUST be an external error.
// It can NOT be an internal error.
assertRuntimeErrorIsExternalError(t, err)
})

t.Run("regular error panicked", func(t *testing.T) {
t.Parallel()

script := []byte(`
access(all) fun foo() {}
pub fun main() {
foo()
}
`)

runtimeInterface := &testRuntimeInterface{
storage: newTestLedger(nil, nil),
meterComputation: func(compKind common.ComputationKind, intensity uint) error {
panic(fmt.Errorf("computation limit exceeded"))
},
}

_, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: common.ScriptLocation{},
},
)

require.Error(t, err)

// Returned error MUST be an external error.
// It can NOT be an internal error.
assertRuntimeErrorIsExternalError(t, err)
})

t.Run("go runtime error panicked", func(t *testing.T) {
t.Parallel()

script := []byte(`
access(all) fun foo() {}
pub fun main() {
foo()
}
`)

runtimeInterface := &testRuntimeInterface{
storage: newTestLedger(nil, nil),
meterComputation: func(compKind common.ComputationKind, intensity uint) error {
// Cause a runtime error
var x any = "hello"
_ = x.(int)
return nil
},
}

_, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: common.ScriptLocation{},
},
)

require.Error(t, err)

// Returned error MUST be an internal error.
assertRuntimeErrorIsInternalError(t, err)
})

t.Run("go runtime error returned", func(t *testing.T) {
t.Parallel()

script := []byte(`
access(all) fun foo() {}
pub fun main() {
foo()
}
`)

runtimeInterface := &testRuntimeInterface{
storage: newTestLedger(nil, nil),
meterComputation: func(compKind common.ComputationKind, intensity uint) (err error) {
// Cause a runtime error. Catch it and return.
var x any = "hello"
defer func() {
if r := recover(); r != nil {
if r, ok := r.(error); ok {
err = r
}
}
}()

_ = x.(int)

return
},
}

_, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: common.ScriptLocation{},
},
)

require.Error(t, err)

// Returned error MUST be an internal error.
assertRuntimeErrorIsInternalError(t, err)
})
}
Loading

0 comments on commit 5ea8284

Please sign in to comment.