Skip to content

Commit

Permalink
Wrap host env errors with external errors
Browse files Browse the repository at this point in the history
  • Loading branch information
SupunS committed Jul 27, 2023
1 parent 181924e commit abb7402
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 32 deletions.
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)
}

Check warning on line 437 in runtime/environment.go

View check run for this annotation

Codecov / codecov/patch

runtime/environment.go#L436-L437

Added lines #L436 - L437 were not covered by tests
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)
}

Check warning on line 592 in runtime/environment.go

View check run for this annotation

Codecov / codecov/patch

runtime/environment.go#L591-L592

Added lines #L591 - L592 were not covered by tests

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)
}

Check warning on line 765 in runtime/environment.go

View check run for this annotation

Codecov / codecov/patch

runtime/environment.go#L764-L765

Added lines #L764 - L765 were not covered by tests
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))

Check warning on line 84 in runtime/events.go

View check run for this annotation

Codecov / codecov/patch

runtime/events.go#L84

Added line #L84 was not covered by tests
}
}
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 abb7402

Please sign in to comment.