Skip to content

Commit

Permalink
Merge pull request #2970 from onflow/sainati/nested-reference-static-…
Browse files Browse the repository at this point in the history
…check

Static check to prevent nested references
  • Loading branch information
dsainati1 authored Dec 14, 2023
2 parents 0ff20e1 + 9034f6d commit ba0e26e
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 4 deletions.
15 changes: 15 additions & 0 deletions runtime/sema/check_reference_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ func (checker *Checker) VisitReferenceExpression(referenceExpression *ast.Refere

referencedType, actualType := checker.visitExpression(referencedExpression, expectedLeftType)

// check that the type of the referenced value is not itself a reference
var requireNoReferenceNesting func(actualType Type)
requireNoReferenceNesting = func(actualType Type) {
switch nestedReference := actualType.(type) {
case *ReferenceType:
checker.report(&NestedReferenceError{
Type: nestedReference,
Range: checker.expressionRange(referenceExpression),
})
case *OptionalType:
requireNoReferenceNesting(nestedReference.Type)
}
}
requireNoReferenceNesting(actualType)

hasErrors := len(checker.errors) > beforeErrors
if !hasErrors {
// If the reference type was an optional type,
Expand Down
17 changes: 17 additions & 0 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4694,3 +4694,20 @@ func (*InvalidTypeParameterizedNonNativeFunctionError) IsUserError() {}
func (e *InvalidTypeParameterizedNonNativeFunctionError) Error() string {
return "invalid type parameters in non-native function"
}

// NestedReferenceError
type NestedReferenceError struct {
Type *ReferenceType
ast.Range
}

var _ SemanticError = &NestedReferenceError{}
var _ errors.UserError = &NestedReferenceError{}

func (*NestedReferenceError) isSemanticError() {}

func (*NestedReferenceError) IsUserError() {}

func (e *NestedReferenceError) Error() string {
return fmt.Sprintf("cannot create a nested reference to value of type %s", e.Type.QualifiedString())
}
60 changes: 60 additions & 0 deletions runtime/tests/checker/reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3061,3 +3061,63 @@ func TestCheckResourceReferenceIndexNilAssignment(t *testing.T) {
require.IsType(t, &sema.InvalidResourceAssignmentError{}, errors[2])
})
}

func TestCheckNestedReference(t *testing.T) {
t.Parallel()

t.Run("basic", func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheck(t, `
fun main() {
let x = &1 as &Int
let y = &x as & &Int
}
`)

errors := RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errors[0])
})

t.Run("type of underlying value checked", func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheck(t, `
fun main() {
let x = &1 as &Int
let y = &x as &AnyStruct
}
`)

errors := RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errors[0])
})

t.Run("optional", func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheck(t, `
fun main() {
let x: &Int? = &1 as &Int
let y = &x as &AnyStruct?
}
`)

errors := RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errors[0])
})

t.Run("nested optional", func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheck(t, `
fun main() {
let x: &Int?? = &1 as &Int
let y = &x as &AnyStruct?
}
`)

errors := RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errors[0])
})
}
37 changes: 33 additions & 4 deletions runtime/tests/interpreter/reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1739,14 +1739,20 @@ func TestInterpretReferenceToReference(t *testing.T) {

t.Parallel()

inter := parseCheckAndInterpret(t, `
inter, err := parseCheckAndInterpretWithOptions(t, `
fun main() {
let x = &1 as &Int
let y = &x as & &Int
}
`)
`, ParseCheckAndInterpretOptions{
HandleCheckerError: func(err error) {
errs := checker.RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errs[0])
},
})
require.NoError(t, err)

_, err := inter.Invoke("main")
_, err = inter.Invoke("main")
RequireError(t, err)

require.ErrorAs(t, err, &interpreter.NestedReferenceError{})
Expand All @@ -1773,11 +1779,34 @@ func TestInterpretReferenceToReference(t *testing.T) {

t.Parallel()

inter := parseCheckAndInterpret(t, `
inter, err := parseCheckAndInterpretWithOptions(t, `
fun main() {
let x: (&Int)? = &1 as &Int
let y: (&(&Int))? = &x
}
`, ParseCheckAndInterpretOptions{
HandleCheckerError: func(err error) {
errs := checker.RequireCheckerErrors(t, err, 1)
require.IsType(t, &sema.NestedReferenceError{}, errs[0])
},
})
require.NoError(t, err)

_, err = inter.Invoke("main")
RequireError(t, err)

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

t.Run("upcast to optional anystruct", func(t *testing.T) {

t.Parallel()

inter := parseCheckAndInterpret(t, `
fun main() {
let x = &1 as &Int as AnyStruct?
let y = &x as &AnyStruct?
}
`)

_, err := inter.Invoke("main")
Expand Down

0 comments on commit ba0e26e

Please sign in to comment.