Skip to content

Commit

Permalink
Merge pull request #2840 from onflow/sainati/entitlement-mapping-assign
Browse files Browse the repository at this point in the history
Require full codomain of map when assigning to mapped field
  • Loading branch information
dsainati1 authored Oct 4, 2023
2 parents d45a8ec + 787fdbe commit b89b670
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 0 deletions.
22 changes: 22 additions & 0 deletions runtime/sema/check_member_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,28 @@ func (checker *Checker) mapAccess(
// pretend that the access succeeds to prevent a redundant access error report
return true, UnauthorizedAccess
}
// when we are in an assignment statement,
// we need full permissions to the map regardless of the input authorization of the reference
// Consider:
//
// entitlement X
// entitlement Y
// entitlement mapping M {
// X -> Insert
// Y -> Remove
// }
// struct S {
// access(M) var member: auth(M) &[T]?
// ...
// }
//
// If we were able to assign a `auth(Insert) &[T]` value to `ref.member` when `ref` has type `auth(X) &S`
// we could use this to then extract a `auth(Insert, Remove) &[T]` reference to that array by accessing `member`
// on an owned copy of `S`. As such, when in an assignment, we return the full codomain here as the "granted authorization"
// of the access expression, since the checker will later enforce that the incoming reference value is a subtype of that full codomain.
if checker.inAssignment {
return true, mappedAccess.Codomain()
}
return true, grantedAccess

case *OptionalType:
Expand Down
71 changes: 71 additions & 0 deletions runtime/tests/checker/entitlements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7369,3 +7369,74 @@ func TestCheckEntitlementMissingInMap(t *testing.T) {
require.IsType(t, &sema.InvalidNonEntitlementTypeInMapError{}, errors[0])
})
}

func TestInterpretMappingEscalation(t *testing.T) {

t.Parallel()

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

_, err := ParseAndCheck(t, `
entitlement X
entitlement Y
entitlement mapping M {
X -> Insert
Y -> Remove
}
struct S {
access(M) var member: auth(M) &[Int]?
init() {
self.member = nil;
}
access(all) fun grantRemovePrivileges(param: auth(Insert) &[Int]): Void{
var selfRef = &self as auth(X) &S;
selfRef.member = param;
}
}
fun main(): Void {
var arr: [Int] = [123];
var arrRef = &arr as auth(Insert) &[Int];
let s = S()
s.grantRemovePrivileges(param: arrRef);
s.member?.removeLast()
}
`)

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

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

_, err := ParseAndCheck(t, `
entitlement X
entitlement Y
entitlement mapping M {
X -> Insert
Y -> Remove
}
struct S {
access(M) var member: auth(M) &[Int]?
init() {
self.member = nil;
}
access(all) fun grantRemovePrivileges(sRef: auth(X) &S, param: auth(Insert) &[Int]): Void{
sRef.member = param;
}
}
fun main(): Void {
var arr: [Int] = [123];
var arrRef = &arr as auth(Insert) &[Int];
let s = S()
s.grantRemovePrivileges(sRef: &s as auth(X) &S, param: arrRef);
s.member?.removeLast()
}
`)

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

}

0 comments on commit b89b670

Please sign in to comment.