Skip to content

Commit

Permalink
Merge pull request #3482 from onflow/bastian/3480-handle-broken-contr…
Browse files Browse the repository at this point in the history
…acts

Handle broken contracts
  • Loading branch information
turbolent authored Jul 29, 2024
2 parents 542618d + a789547 commit e79cdf8
Show file tree
Hide file tree
Showing 5 changed files with 625 additions and 6 deletions.
5 changes: 5 additions & 0 deletions runtime/empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"go.opentelemetry.io/otel/attribute"

"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime/ast"
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
)
Expand Down Expand Up @@ -233,3 +234,7 @@ func (EmptyRuntimeInterface) ResourceOwnerChanged(
func (EmptyRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) {
panic("unexpected call to GenerateAccountID")
}

func (EmptyRuntimeInterface) RecoverProgram(_ *ast.Program, _ common.Location) (*ast.Program, error) {
panic("unexpected call to RecoverProgram")
}
96 changes: 90 additions & 6 deletions runtime/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ package runtime
import (
"time"

"go.opentelemetry.io/otel/attribute"

"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime/activations"

"go.opentelemetry.io/otel/attribute"
"github.com/onflow/cadence/runtime/old_parser"

"github.com/onflow/cadence/runtime/ast"
"github.com/onflow/cadence/runtime/common"
Expand Down Expand Up @@ -465,8 +466,50 @@ func (e *interpreterEnvironment) ParseAndCheckProgram(
)
}

// parseAndCheckProgramWithRecovery parses and checks the given program.
// It first attempts to parse and checks the program as usual.
// If parsing or checking fails, recovery is attempted.
//
// Recovery attempts to parse the contract with the old parser,
// and if it succeeds, uses the program recovery handler
// to produce an elaboration for the old program.
func (e *interpreterEnvironment) parseAndCheckProgramWithRecovery(
code []byte,
location common.Location,
checkedImports importResolutionResults,
) (
program *ast.Program,
elaboration *sema.Elaboration,
err error,
) {
// Attempt to parse and check the program as usual
program, elaboration, err = e.parseAndCheckProgram(
code,
location,
checkedImports,
)
if err == nil {
return program, elaboration, nil
}

// If parsing or checking fails, attempt to recover

recoveredProgram, recoveredElaboration := e.recoverProgram(
code,
location,
checkedImports,
)

// If recovery failed, return the original error
if recoveredProgram == nil || recoveredElaboration == nil {
return program, elaboration, err
}

// If recovery succeeded, return the recovered program and elaboration
return recoveredProgram, recoveredElaboration, nil
}

// parseAndCheckProgram parses and checks the given program.
// If storeProgram is true, it calls Interface.SetProgram.
func (e *interpreterEnvironment) parseAndCheckProgram(
code []byte,
location common.Location,
Expand Down Expand Up @@ -508,6 +551,49 @@ func (e *interpreterEnvironment) parseAndCheckProgram(
return program, elaboration, nil
}

// recoverProgram parses and checks the given program with the old parser,
// and recovers the elaboration from the old program.
func (e *interpreterEnvironment) recoverProgram(
code []byte,
location common.Location,
checkedImports importResolutionResults,
) (
program *ast.Program,
elaboration *sema.Elaboration,
) {
// Parse

var err error
reportMetric(
func() {
program, err = old_parser.ParseProgram(e, code, old_parser.Config{})
},
e.runtimeInterface,
func(metrics Metrics, duration time.Duration) {
metrics.ProgramParsed(location, duration)
},
)
if err != nil {
return nil, nil
}

// Recover elaboration from the old program

errors.WrapPanic(func() {
program, err = e.runtimeInterface.RecoverProgram(program, location)
})
if err != nil || program == nil {
return nil, nil
}

elaboration, err = e.check(location, program, checkedImports)
if err != nil || elaboration == nil {
return nil, nil
}

return program, elaboration
}

func (e *interpreterEnvironment) check(
location common.Location,
program *ast.Program,
Expand Down Expand Up @@ -543,7 +629,6 @@ func (e *interpreterEnvironment) newLocationHandler() sema.LocationHandlerFunc {
errors.WrapPanic(func() {
res, err = e.runtimeInterface.ResolveLocation(identifiers, location)
})

if err != nil {
err = interpreter.WrappedExternalError(err)
}
Expand Down Expand Up @@ -643,7 +728,7 @@ func (e *interpreterEnvironment) getProgram(

e.codesAndPrograms.setCode(location, code)

parsedProgram, elaboration, err := e.parseAndCheckProgram(
parsedProgram, elaboration, err := e.parseAndCheckProgramWithRecovery(
code,
location,
checkedImports,
Expand Down Expand Up @@ -866,7 +951,6 @@ func (e *interpreterEnvironment) newUUIDHandler() interpreter.UUIDHandlerFunc {
errors.WrapPanic(func() {
uuid, err = e.runtimeInterface.GenerateUUID()
})

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

0 comments on commit e79cdf8

Please sign in to comment.