From 03197ec642f43b9818c8fffe925b5c9c7a3d949c Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Thu, 22 Jun 2023 11:56:02 -0700 Subject: [PATCH 1/7] Require insertable/mutatble refrence for index assignment --- runtime/resourcedictionary_test.go | 4 +- runtime/sema/check_assignment.go | 12 ++++ runtime/sema/check_member_expression.go | 8 +-- runtime/sema/check_variable_declaration.go | 6 +- runtime/sema/errors.go | 31 +++++++++ .../tests/checker/arrays_dictionaries_test.go | 69 +++++++++++++++++++ 6 files changed, 123 insertions(+), 7 deletions(-) diff --git a/runtime/resourcedictionary_test.go b/runtime/resourcedictionary_test.go index 035d3e10cb..65b1d85607 100644 --- a/runtime/resourcedictionary_test.go +++ b/runtime/resourcedictionary_test.go @@ -58,7 +58,7 @@ const resourceDictionaryContract = ` pub resource C { - pub(set) var rs: @{String: R} + access(Identity) var rs: @{String: R} init() { self.rs <- {} @@ -166,7 +166,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { transaction { prepare(signer: AuthAccount) { - let c = signer.borrow<&Test.C>(from: /storage/c)! + let c = signer.borrow(from: /storage/c)! c.rs["a"] <-! Test.createR(1) c.rs["b"] <-! Test.createR(2) } diff --git a/runtime/sema/check_assignment.go b/runtime/sema/check_assignment.go index c2c854a465..c49783ff04 100644 --- a/runtime/sema/check_assignment.go +++ b/runtime/sema/check_assignment.go @@ -335,6 +335,18 @@ func (checker *Checker) visitIndexExpressionAssignment( } } + indexExprTypes := checker.Elaboration.IndexExpressionTypes(indexExpression) + indexedRefType, isReference := referenceType(indexExprTypes.IndexedType) + if isReference { + if !insertableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { + checker.report(&UnauthorizedReferenceAssignmentError{ + RequiredAccess: insertableEntitledAccess, + FoundAccess: indexedRefType.Authorization, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, indexExpression), + }) + } + } + if elementType == nil { return InvalidType } diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index 62b604a92b..f7013c4f95 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -112,17 +112,17 @@ func (checker *Checker) getReferenceType(typ Type, substituteAuthorization bool, } func shouldReturnReference(parentType, memberType Type) bool { - if !isReferenceType(parentType) { + if _, isReference := referenceType(parentType); !isReference { return false } return isContainerType(memberType) } -func isReferenceType(typ Type) bool { +func referenceType(typ Type) (*ReferenceType, bool) { unwrappedType := UnwrapOptionalType(typ) - _, isReference := unwrappedType.(*ReferenceType) - return isReference + refType, isReference := unwrappedType.(*ReferenceType) + return refType, isReference } func isContainerType(typ Type) bool { diff --git a/runtime/sema/check_variable_declaration.go b/runtime/sema/check_variable_declaration.go index ba46274f72..971abbbe5d 100644 --- a/runtime/sema/check_variable_declaration.go +++ b/runtime/sema/check_variable_declaration.go @@ -260,7 +260,11 @@ func (checker *Checker) recordReferenceCreation(target, expr ast.Expression) { } func (checker *Checker) recordReference(targetVariable *Variable, expr ast.Expression) { - if targetVariable == nil || !isReferenceType(targetVariable.Type) { + if targetVariable == nil { + return + } + + if _, isReference := referenceType(targetVariable.Type); !isReference { return } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 0114bcb29b..a0c449bba6 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2928,6 +2928,37 @@ func (e *InvalidAssignmentAccessError) SecondaryError() string { ) } +// UnauthorizedReferenceAssignmentError + +type UnauthorizedReferenceAssignmentError struct { + RequiredAccess Access + FoundAccess Access + ast.Range +} + +var _ SemanticError = &UnauthorizedReferenceAssignmentError{} +var _ errors.UserError = &UnauthorizedReferenceAssignmentError{} +var _ errors.SecondaryError = &UnauthorizedReferenceAssignmentError{} + +func (*UnauthorizedReferenceAssignmentError) isSemanticError() {} + +func (*UnauthorizedReferenceAssignmentError) IsUserError() {} + +func (e *UnauthorizedReferenceAssignmentError) Error() string { + return fmt.Sprintf( + "invalid assignment: can only assign to a reference with (%s) access, but found a (%s) reference", + e.RequiredAccess.Description(), + e.FoundAccess.Description(), + ) +} + +func (e *UnauthorizedReferenceAssignmentError) SecondaryError() string { + return fmt.Sprintf( + "consider taking a reference with `%s` access", + e.RequiredAccess.Description(), + ) +} + // InvalidCharacterLiteralError type InvalidCharacterLiteralError struct { diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index fd724544be..6e30181240 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1934,4 +1934,73 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { require.NoError(t, err) }) }) + + t.Run("assignment", func(t *testing.T) { + + t.Run("mutable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as auth(Mutable) &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + require.NoError(t, err) + }) + + t.Run("non auth reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + }) + + t.Run("insertable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as auth(Insertable) &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + require.NoError(t, err) + }) + + t.Run("removable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + }) + }) } From 8b909b389e5e87cc3a2ce4a99c7cb5c483aac73a Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 23 Jun 2023 08:13:52 -0700 Subject: [PATCH 2/7] Update tests --- runtime/account_test.go | 4 +- runtime/capabilitycontrollers_test.go | 19 ++--- runtime/resource_duplicate_test.go | 8 +- runtime/runtime_test.go | 6 +- runtime/sema/check_assignment.go | 15 ++-- .../tests/checker/arrays_dictionaries_test.go | 78 ++++++++++++++++++- 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/runtime/account_test.go b/runtime/account_test.go index 0d4fb5cc4c..554d6f8ee4 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -1480,7 +1480,7 @@ func TestRuntimePublicKey(t *testing.T) { signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 ) - var publickeyRef = &publicKey.publicKey as &[UInt8] + var publickeyRef = &publicKey.publicKey as auth(Insertable) &[UInt8] publickeyRef[0] = 3 return publicKey @@ -1909,7 +1909,7 @@ func TestAuthAccountContracts(t *testing.T) { script := []byte(` transaction { prepare(signer: AuthAccount) { - var namesRef = &signer.contracts.names as &[String] + var namesRef = &signer.contracts.names as auth(Insertable) &[String] namesRef[0] = "baz" assert(signer.contracts.names[0] == "foo") diff --git a/runtime/capabilitycontrollers_test.go b/runtime/capabilitycontrollers_test.go index 7e8ba64427..23a6e021de 100644 --- a/runtime/capabilitycontrollers_test.go +++ b/runtime/capabilitycontrollers_test.go @@ -85,7 +85,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { /// > Our version of quicksort is not the fastest possible, /// > but it's one of the simplest. /// - access(all) fun quickSort(_ items: &[AnyStruct], isLess: fun(Int, Int): Bool) { + access(all) fun quickSort(_ items: auth(Mutable) &[AnyStruct], isLess: fun(Int, Int): Bool) { fun quickSortPart(leftIndex: Int, rightIndex: Int) { @@ -95,6 +95,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { let pivotIndex = (leftIndex + rightIndex) / 2 + items[pivotIndex] <-> items[leftIndex] items[pivotIndex] <-> items[leftIndex] var lastIndex = leftIndex @@ -1802,7 +1803,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { assert(controllers1.length == 3) Test.quickSort( - &controllers1 as &[AnyStruct], + &controllers1 as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers1[i] let b = controllers1[j] @@ -1900,7 +1901,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { assert(controllers1.length == 3) Test.quickSort( - &controllers1 as &[AnyStruct], + &controllers1 as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers1[i] let b = controllers1[j] @@ -2251,7 +2252,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { assert(controllers.length == 3) Test.quickSort( - &controllers as &[AnyStruct], + &controllers as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers[i] let b = controllers[j] @@ -2329,7 +2330,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { assert(controllers.length == 3) Test.quickSort( - &controllers as &[AnyStruct], + &controllers as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers[i] let b = controllers[j] @@ -2584,7 +2585,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { let controllers1Before = signer.capabilities.storage.getControllers(forPath: storagePath1) Test.quickSort( - &controllers1Before as &[AnyStruct], + &controllers1Before as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers1Before[i] let b = controllers1Before[j] @@ -2598,7 +2599,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { let controllers2Before = signer.capabilities.storage.getControllers(forPath: storagePath2) Test.quickSort( - &controllers2Before as &[AnyStruct], + &controllers2Before as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers2Before[i] let b = controllers2Before[j] @@ -2622,7 +2623,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { let controllers1After = signer.capabilities.storage.getControllers(forPath: storagePath1) Test.quickSort( - &controllers1After as &[AnyStruct], + &controllers1After as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers1After[i] let b = controllers1After[j] @@ -2635,7 +2636,7 @@ func TestRuntimeCapabilityControllers(t *testing.T) { let controllers2After = signer.capabilities.storage.getControllers(forPath: storagePath2) Test.quickSort( - &controllers2After as &[AnyStruct], + &controllers2After as auth(Mutable) &[AnyStruct], isLess: fun(i: Int, j: Int): Bool { let a = controllers2After[i] let b = controllers2After[j] diff --git a/runtime/resource_duplicate_test.go b/runtime/resource_duplicate_test.go index adda7138c6..14868f382d 100644 --- a/runtime/resource_duplicate_test.go +++ b/runtime/resource_duplicate_test.go @@ -183,9 +183,9 @@ func TestRuntimeResourceDuplicationUsingDestructorIteration(t *testing.T) { script := ` access(all) resource Vault { access(all) var balance: UFix64 - access(all) var dictRef: &{Bool: Vault}; + access(all) var dictRef: auth(Mutable) &{Bool: Vault}; - init(balance: UFix64, _ dictRef: &{Bool: Vault}) { + init(balance: UFix64, _ dictRef: auth(Mutable) &{Bool: Vault}) { self.balance = balance self.dictRef = dictRef; } @@ -208,7 +208,7 @@ func TestRuntimeResourceDuplicationUsingDestructorIteration(t *testing.T) { access(all) fun main(): UFix64 { let dict: @{Bool: Vault} <- { } - let dictRef = &dict as &{Bool: Vault}; + let dictRef = &dict as auth(Mutable) &{Bool: Vault}; var v1 <- create Vault(balance: 1000.0, dictRef); // This will be duplicated var v2 <- create Vault(balance: 1.0, dictRef); // This will be lost @@ -305,7 +305,7 @@ func TestRuntimeResourceDuplicationUsingDestructorIteration(t *testing.T) { let acc = getAuthAccount(0x1) acc.save(<-dict, to: /storage/foo) - let ref = acc.borrow<&{Int: R}>(from: /storage/foo)! + let ref = acc.borrow(from: /storage/foo)! ref.forEachKey(fun(i: Int): Bool { var r4: @R? <- create R() diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 8885257ddc..55b081c97c 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -7019,7 +7019,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: AuthAccount} = {} - let ref = &dict as &{Int: AnyStruct} + let ref = &dict as auth(Insertable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/private/xxx) } @@ -7054,7 +7054,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: AuthAccount} = {} - let ref = &dict as &{Int: AnyStruct} + let ref = &dict as auth(Insertable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/public/xxx) } @@ -7089,7 +7089,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: PublicAccount} = {} - let ref = &dict as &{Int: AnyStruct} + let ref = &dict as auth(Insertable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/public/xxx) } diff --git a/runtime/sema/check_assignment.go b/runtime/sema/check_assignment.go index 54f6dc7b95..8f8a7bd6b7 100644 --- a/runtime/sema/check_assignment.go +++ b/runtime/sema/check_assignment.go @@ -320,14 +320,13 @@ func (checker *Checker) visitIndexExpressionAssignment( indexExprTypes := checker.Elaboration.IndexExpressionTypes(indexExpression) indexedRefType, isReference := referenceType(indexExprTypes.IndexedType) - if isReference { - if !insertableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { - checker.report(&UnauthorizedReferenceAssignmentError{ - RequiredAccess: insertableEntitledAccess, - FoundAccess: indexedRefType.Authorization, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, indexExpression), - }) - } + + if isReference && !insertableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { + checker.report(&UnauthorizedReferenceAssignmentError{ + RequiredAccess: insertableEntitledAccess, + FoundAccess: indexedRefType.Authorization, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, indexExpression), + }) } if elementType == nil { diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 6e30181240..8fd783ae89 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1486,10 +1486,10 @@ func TestNilAssignmentToDictionary(t *testing.T) { } func TestCheckArrayFunctionEntitlements(t *testing.T) { - t.Parallel() t.Run("inserting functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1571,6 +1571,7 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { }) t.Run("removing functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1652,6 +1653,7 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { }) t.Run("public functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1725,12 +1727,83 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { require.NoError(t, err) }) }) + + t.Run("assignment", func(t *testing.T) { + t.Parallel() + + t.Run("mutable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Mutable) &[String] + arrayRef[0] = "baz" + } + `) + + require.NoError(t, err) + }) + + t.Run("non auth reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as &[String] + arrayRef[0] = "baz" + } + `) + + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + }) + + t.Run("insertable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Insertable) &[String] + arrayRef[0] = "baz" + } + `) + + require.NoError(t, err) + }) + + t.Run("removable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Removable) &[String] + arrayRef[0] = "baz" + } + `) + + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + }) + }) } func TestCheckDictionaryFunctionEntitlements(t *testing.T) { t.Parallel() t.Run("inserting functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1800,6 +1873,7 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { }) t.Run("removing functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1869,6 +1943,7 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { }) t.Run("public functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1936,6 +2011,7 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { }) t.Run("assignment", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() From 97ab8c04d9b7b74cfde91ab97753c1544f63e0de Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 18 Jul 2023 14:03:42 -0700 Subject: [PATCH 3/7] Update tests --- .../tests/checker/external_mutation_test.go | 4 +++- runtime/tests/checker/member_test.go | 8 +++++--- runtime/tests/checker/reference_test.go | 16 +++++++++++---- .../interpreter/container_mutation_test.go | 4 ++-- runtime/tests/interpreter/member_test.go | 2 +- runtime/tests/interpreter/reference_test.go | 20 +++++++++---------- runtime/tests/interpreter/string_test.go | 2 +- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/runtime/tests/checker/external_mutation_test.go b/runtime/tests/checker/external_mutation_test.go index cb412273d9..71ea9fe869 100644 --- a/runtime/tests/checker/external_mutation_test.go +++ b/runtime/tests/checker/external_mutation_test.go @@ -723,7 +723,9 @@ func TestCheckMutationThroughInnerReference(t *testing.T) { } `, ) - require.NoError(t, err) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.UnauthorizedReferenceAssignmentError{}, errs[0]) }) } diff --git a/runtime/tests/checker/member_test.go b/runtime/tests/checker/member_test.go index fea5ef1246..ba3cb31d16 100644 --- a/runtime/tests/checker/member_test.go +++ b/runtime/tests/checker/member_test.go @@ -878,9 +878,11 @@ func TestCheckMemberAccess(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 4) + assert.IsType(t, &sema.UnauthorizedReferenceAssignmentError{}, errs[0]) + assert.IsType(t, &sema.UnauthorizedReferenceAssignmentError{}, errs[1]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[2]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[3]) }) t.Run("all member types", func(t *testing.T) { diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index cf53c76e47..6d8cf7f380 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -2742,14 +2742,19 @@ func TestCheckReferenceUseAfterCopy(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 4) invalidatedRefError := &sema.InvalidatedResourceReferenceError{} assert.ErrorAs(t, errs[0], &invalidatedRefError) - assert.ErrorAs(t, errs[1], &invalidatedRefError) + + unauthorizedReferenceAssignmentError := &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errs[1], &unauthorizedReferenceAssignmentError) + + assert.ErrorAs(t, errs[2], &invalidatedRefError) typeMismatchError := &sema.TypeMismatchError{} - assert.ErrorAs(t, errs[2], &typeMismatchError) + assert.ErrorAs(t, errs[3], &typeMismatchError) + }) t.Run("resource array, remove", func(t *testing.T) { @@ -2790,9 +2795,12 @@ func TestCheckReferenceUseAfterCopy(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) invalidatedRefError := &sema.InvalidatedResourceReferenceError{} assert.ErrorAs(t, errs[0], &invalidatedRefError) + + unauthorizedReferenceAssignmentError := &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errs[1], &unauthorizedReferenceAssignmentError) }) t.Run("resource dictionary, remove", func(t *testing.T) { diff --git a/runtime/tests/interpreter/container_mutation_test.go b/runtime/tests/interpreter/container_mutation_test.go index e558539c5a..36e45fb159 100644 --- a/runtime/tests/interpreter/container_mutation_test.go +++ b/runtime/tests/interpreter/container_mutation_test.go @@ -288,7 +288,7 @@ func TestArrayMutation(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test() { let names: [AnyStruct] = ["foo", "bar"] as [String] - let namesRef = &names as &[AnyStruct] + let namesRef = &names as auth(Insertable) &[AnyStruct] namesRef[0] = 5 } `) @@ -667,7 +667,7 @@ func TestDictionaryMutation(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test() { let names: {String: AnyStruct} = {"foo": "bar"} as {String: String} - let namesRef = &names as &{String: AnyStruct} + let namesRef = &names as auth(Insertable) &{String: AnyStruct} namesRef["foo"] = 5 } `) diff --git a/runtime/tests/interpreter/member_test.go b/runtime/tests/interpreter/member_test.go index 1900aeb6f6..7108f72575 100644 --- a/runtime/tests/interpreter/member_test.go +++ b/runtime/tests/interpreter/member_test.go @@ -1054,7 +1054,7 @@ func TestInterpretMemberAccess(t *testing.T) { fun test() { let dict: {String: AnyStruct} = {"foo": Foo(), "bar": Foo()} - let dictRef = &dict as &{String: AnyStruct} + let dictRef = &dict as auth(Insertable) &{String: AnyStruct} dictRef["foo"] <-> dictRef["bar"] } diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index 8ef8e32366..e02f0877d2 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -113,7 +113,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: &S1} = {} - let dictRef = &dict as &{Int: &AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} let s2 = S2() dictRef[0] = &s2 as &AnyStruct @@ -148,7 +148,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: S1} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = S2() @@ -186,7 +186,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: &S1} = {} - let dictRef = &dict as &{Int: &AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} let s2 = S2() dictRef[0] = &s2 as &AnyStruct @@ -225,7 +225,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: S1} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = S2() @@ -267,7 +267,7 @@ func TestInterpretContainerVariance(t *testing.T) { let s2 = S2() - let dictRef = &dict as &{Int: &AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} dictRef[0] = &s2 as &AnyStruct dict.values[0].value = 1 @@ -308,7 +308,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: S1} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = S2() @@ -340,7 +340,7 @@ func TestInterpretContainerVariance(t *testing.T) { let s2 = S2() - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = s2 let x = dict.values[0] @@ -369,7 +369,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: fun(): Int} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = f2 @@ -393,7 +393,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: [UInt8]} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = "not an [UInt8] array, but a String" @@ -417,7 +417,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: [UInt8]} = {} - let dictRef = &dict as &{Int: AnyStruct} + let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} dictRef[0] = "not an [UInt8] array, but a String" diff --git a/runtime/tests/interpreter/string_test.go b/runtime/tests/interpreter/string_test.go index 44d3f0ee16..d230e8a5cb 100644 --- a/runtime/tests/interpreter/string_test.go +++ b/runtime/tests/interpreter/string_test.go @@ -36,7 +36,7 @@ func TestInterpretRecursiveValueString(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test(): AnyStruct { let map: {String: AnyStruct} = {} - let mapRef = &map as &{String: AnyStruct} + let mapRef = &map as auth(Insertable) &{String: AnyStruct} mapRef["mapRef"] = mapRef return map } From 57821ec4a99dc4619058fa2e836eb657b33027ad Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 18 Jul 2023 14:15:11 -0700 Subject: [PATCH 4/7] Add more tests --- .../tests/checker/arrays_dictionaries_test.go | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 8fd783ae89..f8a52e553b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1797,6 +1797,44 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { assert.ErrorAs(t, errors[0], &invalidAccessError) }) }) + + t.Run("swap", func(t *testing.T) { + t.Parallel() + + t.Run("mutable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Mutable) &[String] + arrayRef[0] <-> arrayRef[1] + } + `) + + require.NoError(t, err) + }) + + t.Run("non auth reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as &[String] + arrayRef[0] <-> arrayRef[1] + } + `) + + errors := RequireCheckerErrors(t, err, 2) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + assert.ErrorAs(t, errors[1], &invalidAccessError) + }) + }) } func TestCheckDictionaryFunctionEntitlements(t *testing.T) { @@ -2079,4 +2117,42 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { assert.ErrorAs(t, errors[0], &invalidAccessError) }) }) + + t.Run("swap", func(t *testing.T) { + t.Parallel() + + t.Run("mutable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: AnyStruct} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as auth(Mutable) &{String: AnyStruct} + dictionaryRef["one"] <-> dictionaryRef["two"] + } + `) + + require.NoError(t, err) + }) + + t.Run("non auth reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as &{String: String} + dictionaryRef["one"] <-> dictionaryRef["two"] + } + `) + + errors := RequireCheckerErrors(t, err, 2) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + assert.ErrorAs(t, errors[1], &invalidAccessError) + }) + }) } From 34b30d513132215b63e340d3201490bdd6e1e4d5 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Wed, 19 Jul 2023 11:52:54 -0700 Subject: [PATCH 5/7] Requrie mutablity entitlements for index assignment --- runtime/account_test.go | 4 +- runtime/runtime_test.go | 6 +- runtime/sema/check_assignment.go | 16 +++- runtime/sema/errors.go | 21 +++-- .../tests/checker/arrays_dictionaries_test.go | 80 ++++++++++++++++++- .../interpreter/container_mutation_test.go | 4 +- runtime/tests/interpreter/member_test.go | 2 +- runtime/tests/interpreter/reference_test.go | 20 ++--- runtime/tests/interpreter/string_test.go | 2 +- 9 files changed, 124 insertions(+), 31 deletions(-) diff --git a/runtime/account_test.go b/runtime/account_test.go index 554d6f8ee4..aa88143305 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -1480,7 +1480,7 @@ func TestRuntimePublicKey(t *testing.T) { signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 ) - var publickeyRef = &publicKey.publicKey as auth(Insertable) &[UInt8] + var publickeyRef = &publicKey.publicKey as auth(Mutable) &[UInt8] publickeyRef[0] = 3 return publicKey @@ -1909,7 +1909,7 @@ func TestAuthAccountContracts(t *testing.T) { script := []byte(` transaction { prepare(signer: AuthAccount) { - var namesRef = &signer.contracts.names as auth(Insertable) &[String] + var namesRef = &signer.contracts.names as auth(Mutable) &[String] namesRef[0] = "baz" assert(signer.contracts.names[0] == "foo") diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 55b081c97c..3eebd0ff9f 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -7019,7 +7019,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: AuthAccount} = {} - let ref = &dict as auth(Insertable) &{Int: AnyStruct} + let ref = &dict as auth(Mutable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/private/xxx) } @@ -7054,7 +7054,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: AuthAccount} = {} - let ref = &dict as auth(Insertable) &{Int: AnyStruct} + let ref = &dict as auth(Mutable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/public/xxx) } @@ -7089,7 +7089,7 @@ func TestRuntimeGetCapability(t *testing.T) { script := []byte(` access(all) fun main(): Capability { let dict: {Int: PublicAccount} = {} - let ref = &dict as auth(Insertable) &{Int: AnyStruct} + let ref = &dict as auth(Mutable) &{Int: AnyStruct} ref[0] = getAccount(0x01) as AnyStruct return dict.values[0].getCapability(/public/xxx) } diff --git a/runtime/sema/check_assignment.go b/runtime/sema/check_assignment.go index 8f8a7bd6b7..81d4275d96 100644 --- a/runtime/sema/check_assignment.go +++ b/runtime/sema/check_assignment.go @@ -312,6 +312,16 @@ func (checker *Checker) visitIdentifierExpressionAssignment( return variable.Type } +var mutableEntitledAccess = NewEntitlementSetAccess( + []*EntitlementType{MutableEntitlement}, + Disjunction, +) + +var insertableAndRemovableEntitledAccess = NewEntitlementSetAccess( + []*EntitlementType{InsertableEntitlement, RemovableEntitlement}, + Conjunction, +) + func (checker *Checker) visitIndexExpressionAssignment( indexExpression *ast.IndexExpression, ) (elementType Type) { @@ -321,9 +331,11 @@ func (checker *Checker) visitIndexExpressionAssignment( indexExprTypes := checker.Elaboration.IndexExpressionTypes(indexExpression) indexedRefType, isReference := referenceType(indexExprTypes.IndexedType) - if isReference && !insertableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { + if isReference && + !mutableEntitledAccess.PermitsAccess(indexedRefType.Authorization) && + !insertableAndRemovableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { checker.report(&UnauthorizedReferenceAssignmentError{ - RequiredAccess: insertableEntitledAccess, + RequiredAccess: [2]Access{mutableEntitledAccess, insertableAndRemovableEntitledAccess}, FoundAccess: indexedRefType.Authorization, Range: ast.NewRangeFromPositioned(checker.memoryGauge, indexExpression), }) diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 755c9e0b1e..356d4a20c2 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2932,7 +2932,7 @@ func (e *InvalidAssignmentAccessError) SecondaryError() string { // UnauthorizedReferenceAssignmentError type UnauthorizedReferenceAssignmentError struct { - RequiredAccess Access + RequiredAccess [2]Access FoundAccess Access ast.Range } @@ -2946,17 +2946,26 @@ func (*UnauthorizedReferenceAssignmentError) isSemanticError() {} func (*UnauthorizedReferenceAssignmentError) IsUserError() {} func (e *UnauthorizedReferenceAssignmentError) Error() string { + var foundAccess string + if e.FoundAccess == UnauthorizedAccess { + foundAccess = "non-auth" + } else { + foundAccess = fmt.Sprintf("(%s)", e.FoundAccess.Description()) + } + return fmt.Sprintf( - "invalid assignment: can only assign to a reference with (%s) access, but found a (%s) reference", - e.RequiredAccess.Description(), - e.FoundAccess.Description(), + "invalid assignment: can only assign to a reference with (%s) or (%s) access, but found a %s reference", + e.RequiredAccess[0].Description(), + e.RequiredAccess[1].Description(), + foundAccess, ) } func (e *UnauthorizedReferenceAssignmentError) SecondaryError() string { return fmt.Sprintf( - "consider taking a reference with `%s` access", - e.RequiredAccess.Description(), + "consider taking a reference with `%s` or `%s` access", + e.RequiredAccess[0].Description(), + e.RequiredAccess[1].Description(), ) } diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index f8a52e553b..75deaab115 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1762,6 +1762,12 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a non-auth reference", + ) }) t.Run("insertable reference", func(t *testing.T) { @@ -1776,7 +1782,16 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { } `) - require.NoError(t, err) + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a (Insertable) reference", + ) }) t.Run("removable reference", func(t *testing.T) { @@ -1795,6 +1810,27 @@ func TestCheckArrayFunctionEntitlements(t *testing.T) { var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a (Removable) reference", + ) + }) + + t.Run("insertable and removable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Insertable, Removable) &[String] + arrayRef[0] = "baz" + } + `) + + require.NoError(t, err) }) }) @@ -2082,6 +2118,12 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a non-auth reference", + ) }) t.Run("insertable reference", func(t *testing.T) { @@ -2091,12 +2133,21 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} fun test() { - var dictionaryRef = &dictionary as auth(Insertable) &{String: String} + var dictionaryRef = &dictionary as auth(Removable) &{String: String} dictionaryRef["three"] = "baz" } `) - require.NoError(t, err) + errors := RequireCheckerErrors(t, err, 1) + + var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} + assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a (Removable) reference", + ) }) t.Run("removable reference", func(t *testing.T) { @@ -2106,7 +2157,7 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} fun test() { - var dictionaryRef = &dictionary as &{String: String} + var dictionaryRef = &dictionary as auth(Insertable) &{String: String} dictionaryRef["three"] = "baz" } `) @@ -2115,6 +2166,27 @@ func TestCheckDictionaryFunctionEntitlements(t *testing.T) { var invalidAccessError = &sema.UnauthorizedReferenceAssignmentError{} assert.ErrorAs(t, errors[0], &invalidAccessError) + + assert.Contains( + t, + errors[0].Error(), + "can only assign to a reference with (Mutable) or (Insertable, Removable) access, but found a (Insertable) reference", + ) + }) + + t.Run("insertable and removable reference", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as auth(Insertable, Removable) &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + require.NoError(t, err) }) }) diff --git a/runtime/tests/interpreter/container_mutation_test.go b/runtime/tests/interpreter/container_mutation_test.go index 36e45fb159..3e94d98b01 100644 --- a/runtime/tests/interpreter/container_mutation_test.go +++ b/runtime/tests/interpreter/container_mutation_test.go @@ -288,7 +288,7 @@ func TestArrayMutation(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test() { let names: [AnyStruct] = ["foo", "bar"] as [String] - let namesRef = &names as auth(Insertable) &[AnyStruct] + let namesRef = &names as auth(Mutable) &[AnyStruct] namesRef[0] = 5 } `) @@ -667,7 +667,7 @@ func TestDictionaryMutation(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test() { let names: {String: AnyStruct} = {"foo": "bar"} as {String: String} - let namesRef = &names as auth(Insertable) &{String: AnyStruct} + let namesRef = &names as auth(Mutable) &{String: AnyStruct} namesRef["foo"] = 5 } `) diff --git a/runtime/tests/interpreter/member_test.go b/runtime/tests/interpreter/member_test.go index 7108f72575..58751535ba 100644 --- a/runtime/tests/interpreter/member_test.go +++ b/runtime/tests/interpreter/member_test.go @@ -1054,7 +1054,7 @@ func TestInterpretMemberAccess(t *testing.T) { fun test() { let dict: {String: AnyStruct} = {"foo": Foo(), "bar": Foo()} - let dictRef = &dict as auth(Insertable) &{String: AnyStruct} + let dictRef = &dict as auth(Mutable) &{String: AnyStruct} dictRef["foo"] <-> dictRef["bar"] } diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index e02f0877d2..db39a157dc 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -113,7 +113,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: &S1} = {} - let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: &AnyStruct} let s2 = S2() dictRef[0] = &s2 as &AnyStruct @@ -148,7 +148,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: S1} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = S2() @@ -186,7 +186,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: &S1} = {} - let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: &AnyStruct} let s2 = S2() dictRef[0] = &s2 as &AnyStruct @@ -225,7 +225,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: S1} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = S2() @@ -267,7 +267,7 @@ func TestInterpretContainerVariance(t *testing.T) { let s2 = S2() - let dictRef = &dict as auth(Insertable) &{Int: &AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: &AnyStruct} dictRef[0] = &s2 as &AnyStruct dict.values[0].value = 1 @@ -308,7 +308,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: S1} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = S2() @@ -340,7 +340,7 @@ func TestInterpretContainerVariance(t *testing.T) { let s2 = S2() - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = s2 let x = dict.values[0] @@ -369,7 +369,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test(): Int { let dict: {Int: fun(): Int} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = f2 @@ -393,7 +393,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: [UInt8]} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = "not an [UInt8] array, but a String" @@ -417,7 +417,7 @@ func TestInterpretContainerVariance(t *testing.T) { fun test() { let dict: {Int: [UInt8]} = {} - let dictRef = &dict as auth(Insertable) &{Int: AnyStruct} + let dictRef = &dict as auth(Mutable) &{Int: AnyStruct} dictRef[0] = "not an [UInt8] array, but a String" diff --git a/runtime/tests/interpreter/string_test.go b/runtime/tests/interpreter/string_test.go index d230e8a5cb..1057649306 100644 --- a/runtime/tests/interpreter/string_test.go +++ b/runtime/tests/interpreter/string_test.go @@ -36,7 +36,7 @@ func TestInterpretRecursiveValueString(t *testing.T) { inter := parseCheckAndInterpret(t, ` fun test(): AnyStruct { let map: {String: AnyStruct} = {} - let mapRef = &map as auth(Insertable) &{String: AnyStruct} + let mapRef = &map as auth(Mutable) &{String: AnyStruct} mapRef["mapRef"] = mapRef return map } From ae64ba2bc4621b3442437a7e790b7ff609197f36 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 1 Aug 2023 13:24:12 -0700 Subject: [PATCH 6/7] Conform dictionaries and arrays to EntitlementSupportingType --- runtime/sema/type.go | 24 ++++++++ runtime/tests/checker/entitlements_test.go | 67 ++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 0a8b8c8a15..2f3fac6be4 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -1877,6 +1877,7 @@ const UFix64TypeMaxFractional = fixedpoint.UFix64TypeMaxFractional type ArrayType interface { ValueIndexableType + EntitlementSupportingType isArrayType() } @@ -2367,6 +2368,7 @@ type VariableSizedType struct { var _ Type = &VariableSizedType{} var _ ArrayType = &VariableSizedType{} var _ ValueIndexableType = &VariableSizedType{} +var _ EntitlementSupportingType = &VariableSizedType{} func NewVariableSizedType(memoryGauge common.MemoryGauge, typ Type) *VariableSizedType { common.UseMemory(memoryGauge, common.VariableSizedSemaTypeMemoryUsage) @@ -2508,6 +2510,18 @@ func (t *VariableSizedType) Resolve(typeArguments *TypeParameterTypeOrderedMap) } } +func (t *VariableSizedType) SupportedEntitlements() *EntitlementOrderedSet { + return arrayDictionaryEntitlements +} + +var arrayDictionaryEntitlements = func() *EntitlementOrderedSet { + set := orderedmap.New[EntitlementOrderedSet](3) + set.Set(MutableEntitlement, struct{}{}) + set.Set(InsertableEntitlement, struct{}{}) + set.Set(RemovableEntitlement, struct{}{}) + return set +}() + // ConstantSizedType is a constant sized array type type ConstantSizedType struct { Type Type @@ -2519,6 +2533,7 @@ type ConstantSizedType struct { var _ Type = &ConstantSizedType{} var _ ArrayType = &ConstantSizedType{} var _ ValueIndexableType = &ConstantSizedType{} +var _ EntitlementSupportingType = &ConstantSizedType{} func NewConstantSizedType(memoryGauge common.MemoryGauge, typ Type, size int64) *ConstantSizedType { common.UseMemory(memoryGauge, common.ConstantSizedSemaTypeMemoryUsage) @@ -2668,6 +2683,10 @@ func (t *ConstantSizedType) Resolve(typeArguments *TypeParameterTypeOrderedMap) } } +func (t *ConstantSizedType) SupportedEntitlements() *EntitlementOrderedSet { + return arrayDictionaryEntitlements +} + // Parameter func formatParameter(spaces bool, label, identifier, typeAnnotation string) string { @@ -5141,6 +5160,7 @@ type DictionaryType struct { var _ Type = &DictionaryType{} var _ ValueIndexableType = &DictionaryType{} +var _ EntitlementSupportingType = &DictionaryType{} func NewDictionaryType(memoryGauge common.MemoryGauge, keyType, valueType Type) *DictionaryType { common.UseMemory(memoryGauge, common.DictionarySemaTypeMemoryUsage) @@ -5571,6 +5591,10 @@ func (t *DictionaryType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Typ } } +func (t *DictionaryType) SupportedEntitlements() *EntitlementOrderedSet { + return arrayDictionaryEntitlements +} + // ReferenceType represents the reference to a value type ReferenceType struct { Type Type diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index e913ad977d..bbf3e270c4 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4585,6 +4585,7 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { func TestCheckEntitlementConditions(t *testing.T) { t.Parallel() + t.Run("use of function on owned value", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -4808,6 +4809,72 @@ func TestCheckEntitlementConditions(t *testing.T) { assert.NoError(t, err) }) + + t.Run("result value usage, variable-sized resource array", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R {} + + fun foo(r: @[R]): @[R] { + post { + bar(result): "" + } + return <-r + } + + // 'result' variable should have all the entitlements available for arrays. + view fun bar(_ r: auth(Mutable, Insertable, Removable) &[R]): Bool { + return true + } + `) + + assert.NoError(t, err) + }) + + t.Run("result value usage, constant-sized resource array", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R {} + + fun foo(r: @[R; 5]): @[R; 5] { + post { + bar(result): "" + } + return <-r + } + + // 'result' variable should have all the entitlements available for arrays. + view fun bar(_ r: auth(Mutable, Insertable, Removable) &[R; 5]): Bool { + return true + } + `) + + assert.NoError(t, err) + }) + + t.Run("result value usage, resource dictionary", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R {} + + fun foo(r: @{String:R}): @{String:R} { + post { + bar(result): "" + } + return <-r + } + + // 'result' variable should have all the entitlements available for dictionaries. + view fun bar(_ r: auth(Mutable, Insertable, Removable) &{String:R}): Bool { + return true + } + `) + + assert.NoError(t, err) + }) } func TestCheckEntitledWriteAndMutateNotAllowed(t *testing.T) { From c142c39b8556ec9812b186bbd30a3fbd7f60e633 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 1 Aug 2023 13:39:44 -0700 Subject: [PATCH 7/7] Remove redundant nil check --- runtime/sema/check_member_expression.go | 4 ---- runtime/tests/checker/member_test.go | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index e50691de04..4ac61f6942 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -112,10 +112,6 @@ func (checker *Checker) getReferenceType(typ Type, substituteAuthorization bool, } func shouldReturnReference(parentType, memberType Type) bool { - if memberType == nil { - return false - } - if _, isReference := referenceType(parentType); !isReference { return false } diff --git a/runtime/tests/checker/member_test.go b/runtime/tests/checker/member_test.go index ba3cb31d16..6d35d7b4c2 100644 --- a/runtime/tests/checker/member_test.go +++ b/runtime/tests/checker/member_test.go @@ -527,6 +527,24 @@ func TestCheckMemberAccess(t *testing.T) { require.NoError(t, err) }) + t.Run("composite reference, non-existing field", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct Test {} + + fun test() { + let test = Test() + let testRef = &test as &Test + var x: Int = testRef.x + } + `) + + errs := RequireCheckerErrors(t, err, 1) + var memberErr *sema.NotDeclaredMemberError + require.ErrorAs(t, errs[0], &memberErr) + }) + t.Run("composite reference, function", func(t *testing.T) { t.Parallel()