Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle broken contracts #3482

Merged
merged 9 commits into from
Jul 29, 2024
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
Loading