diff --git a/runtime/account_test.go b/runtime/account_test.go index 0d4fb5cc4c..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 &[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 &[String] + var namesRef = &signer.contracts.names as auth(Mutable) &[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..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 &{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 &{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 &{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 41e9238605..81d4275d96 100644 --- a/runtime/sema/check_assignment.go +++ b/runtime/sema/check_assignment.go @@ -312,12 +312,35 @@ 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) { elementType = checker.visitIndexExpression(indexExpression, true) + indexExprTypes := checker.Elaboration.IndexExpressionTypes(indexExpression) + indexedRefType, isReference := referenceType(indexExprTypes.IndexedType) + + if isReference && + !mutableEntitledAccess.PermitsAccess(indexedRefType.Authorization) && + !insertableAndRemovableEntitledAccess.PermitsAccess(indexedRefType.Authorization) { + checker.report(&UnauthorizedReferenceAssignmentError{ + RequiredAccess: [2]Access{mutableEntitledAccess, insertableAndRemovableEntitledAccess}, + 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 92d4011015..4ac61f6942 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 memberType == nil || !isReferenceType(parentType) { + if _, isReference := referenceType(parentType); !isReference { return false } return memberType.ContainFieldsOrElements() } -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 (checker *Checker) visitMember(expression *ast.MemberExpression) (accessedType Type, resultingType Type, member *Member, isOptional 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 0b24f0a2a0..356d4a20c2 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2929,6 +2929,46 @@ func (e *InvalidAssignmentAccessError) SecondaryError() string { ) } +// UnauthorizedReferenceAssignmentError + +type UnauthorizedReferenceAssignmentError struct { + RequiredAccess [2]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 { + 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) 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` or `%s` access", + e.RequiredAccess[0].Description(), + e.RequiredAccess[1].Description(), + ) +} + // InvalidCharacterLiteralError type InvalidCharacterLiteralError struct { 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/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index fd724544be..75deaab115 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,157 @@ 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) + + 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) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let array: [String] = ["foo", "bar"] + + fun test() { + var arrayRef = &array as auth(Insertable) &[String] + arrayRef[0] = "baz" + } + `) + + 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) { + 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) + + 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) + }) + }) + + 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) { t.Parallel() t.Run("inserting functions", func(t *testing.T) { + t.Parallel() t.Run("mutable reference", func(t *testing.T) { t.Parallel() @@ -1800,6 +1947,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 +2017,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() @@ -1934,4 +2083,148 @@ func TestCheckDictionaryFunctionEntitlements(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 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) + + 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) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + let dictionary: {String: String} = {"one" : "foo", "two" : "bar"} + + fun test() { + var dictionaryRef = &dictionary as auth(Removable) &{String: String} + dictionaryRef["three"] = "baz" + } + `) + + 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) { + 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" + } + `) + + 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("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) + }) + }) + + 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) + }) + }) } 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) { 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..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() @@ -878,9 +896,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..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 &[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 &{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 1900aeb6f6..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 &{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 8ef8e32366..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 &{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 &{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 &{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 &{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 &{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 &{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 &{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 &{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 &{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 &{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 44d3f0ee16..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 &{String: AnyStruct} + let mapRef = &map as auth(Mutable) &{String: AnyStruct} mapRef["mapRef"] = mapRef return map }