Skip to content

Commit

Permalink
Merge pull request #3406 from onflow/sainati/identity-mapping-initial…
Browse files Browse the repository at this point in the history
…izer

Add access top type to model inaccessible access for identity maps
  • Loading branch information
dsainati1 authored Jun 11, 2024
2 parents b7981e5 + c3bfda6 commit 24b0b11
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 21 deletions.
5 changes: 5 additions & 0 deletions runtime/ast/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ type PrimitiveAccess uint8

const (
AccessNotSpecified PrimitiveAccess = iota
AccessNone // "top" access, only used for mapping operations, not actually expressible in the language
AccessSelf
AccessContract
AccessAccount
Expand Down Expand Up @@ -256,6 +257,8 @@ func (a PrimitiveAccess) Keyword() string {
return "access(contract)"
case AccessPubSettableLegacy:
return "pub(set)"
case AccessNone:
return "inaccessible"
}

panic(errors.NewUnreachableError())
Expand All @@ -275,6 +278,8 @@ func (a PrimitiveAccess) Description() string {
return "contract"
case AccessPubSettableLegacy:
return "legacy public settable"
case AccessNone:
return "inaccessible"
}

panic(errors.NewUnreachableError())
Expand Down
15 changes: 8 additions & 7 deletions runtime/ast/primitiveaccess_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions runtime/interpreter/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,12 @@ func (d TypeDecoder) decodeStaticAuthorization() (Authorization, error) {
return nil, err
}
return UnauthorizedAccess, nil
case CBORTagInaccessibleStaticAuthorization:
err := d.decoder.DecodeNil()
if err != nil {
return nil, err
}
return InaccessibleAccess, nil
case CBORTagEntitlementMapStaticAuthorization:
typeID, err := d.decoder.DecodeString()
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions runtime/interpreter/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ const (
CBORTagUnauthorizedStaticAuthorization
CBORTagEntitlementMapStaticAuthorization
CBORTagEntitlementSetStaticAuthorization
CBORTagInaccessibleStaticAuthorization

_
_
_
_

CBORTagInclusiveRangeStaticType

// !!! *WARNING* !!!
Expand Down Expand Up @@ -1317,6 +1324,17 @@ func (t Unauthorized) Encode(e *cbor.StreamEncoder) error {
return e.EncodeNil()
}

func (t Inaccessible) Encode(e *cbor.StreamEncoder) error {
err := e.EncodeRawBytes([]byte{
// tag number
0xd8, CBORTagInaccessibleStaticAuthorization,
})
if err != nil {
return err
}
return e.EncodeNil()
}

func (a EntitlementMapAuthorization) Encode(e *cbor.StreamEncoder) error {
err := e.EncodeRawBytes([]byte{
// tag number
Expand Down
2 changes: 1 addition & 1 deletion runtime/interpreter/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3587,7 +3587,7 @@ func TestCBORTagValue(t *testing.T) {
t.Parallel()

t.Run("No new types added in between", func(t *testing.T) {
require.Equal(t, byte(226), byte(CBORTag_Count))
require.Equal(t, byte(231), byte(CBORTag_Count))
})
}

Expand Down
2 changes: 1 addition & 1 deletion runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4991,7 +4991,7 @@ func (interpreter *Interpreter) mapMemberValueAuthorization(

default:
var access sema.Access
if mappedAccess.Type == sema.IdentityType {
if mappedAccess.Type.IncludesIdentity {
access = sema.AllSupportedEntitlements(resultingType)
}

Expand Down
40 changes: 36 additions & 4 deletions runtime/interpreter/statictype.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,11 +643,11 @@ var FullyEntitledAccountAccess = ConvertSemaAccessToStaticAuthorization(nil, sem
func (Unauthorized) isAuthorization() {}

func (Unauthorized) String() string {
return ""
return "Unauthorized"
}

func (Unauthorized) MeteredString(_ common.MemoryGauge) string {
return ""
func (a Unauthorized) MeteredString(_ common.MemoryGauge) string {
return "Unauthorized"
}

func (Unauthorized) ID() TypeID {
Expand All @@ -659,6 +659,29 @@ func (Unauthorized) Equal(auth Authorization) bool {
return ok
}

type Inaccessible struct{}

var InaccessibleAccess Authorization = Inaccessible{}

func (Inaccessible) isAuthorization() {}

func (Inaccessible) String() string {
return "Inaccessible"
}

func (Inaccessible) MeteredString(_ common.MemoryGauge) string {
return "Inaccessible"
}

func (Inaccessible) ID() TypeID {
panic(errors.NewUnreachableError())
}

func (Inaccessible) Equal(auth Authorization) bool {
_, ok := auth.(Inaccessible)
return ok
}

type EntitlementSetAuthorization struct {
Entitlements *sema.TypeIDOrderedSet
SetKind sema.EntitlementSetKind
Expand Down Expand Up @@ -831,7 +854,10 @@ func (t *ReferenceStaticType) String() string {

func (t *ReferenceStaticType) MeteredString(memoryGauge common.MemoryGauge) string {
typeStr := t.ReferencedType.MeteredString(memoryGauge)
authString := t.Authorization.MeteredString(memoryGauge)
authString := ""
if !t.Authorization.Equal(InaccessibleAccess) && !t.Authorization.Equal(UnauthorizedAccess) {
authString = t.Authorization.MeteredString(memoryGauge)
}

common.UseMemory(memoryGauge, common.NewRawStringMemoryUsage(len(typeStr)+1+len(authString)))
return fmt.Sprintf("%s&%s", authString, typeStr)
Expand Down Expand Up @@ -1049,6 +1075,9 @@ func ConvertSemaAccessToStaticAuthorization(
if access.Equal(sema.UnauthorizedAccess) {
return UnauthorizedAccess
}
if access.Equal(sema.InaccessibleAccess) {
return InaccessibleAccess
}

case sema.EntitlementSetAccess:
var entitlements []common.TypeID
Expand Down Expand Up @@ -1124,6 +1153,9 @@ func ConvertStaticAuthorizationToSemaAccess(
case Unauthorized:
return sema.UnauthorizedAccess, nil

case Inaccessible:
return sema.InaccessibleAccess, nil

case EntitlementMapAuthorization:
entitlement, err := handler.GetEntitlementMapType(auth.TypeID)
if err != nil {
Expand Down
11 changes: 10 additions & 1 deletion runtime/sema/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,13 @@ func (e *EntitlementMapAccess) PermitsAccess(other Access) bool {
// the input entitlement. It is only safe for `R` to give out these entitlements if it actually
// possesses them, so we require the initializing value to have every possible entitlement that may
// be produced by the map
//
// However, if the map is or includes the `Identity`, there is no possible set that is permitted by
// this map, since the theoretical codomain of the Identity map is infinite
case EntitlementSetAccess:
if e.Type.IncludesIdentity {
return false
}
return e.Codomain().PermitsAccess(otherAccess)
default:
return false
Expand All @@ -346,7 +352,7 @@ func (e *EntitlementMapAccess) Domain() EntitlementSetAccess {
return e.domain
}

func (e *EntitlementMapAccess) Codomain() EntitlementSetAccess {
func (e *EntitlementMapAccess) Codomain() Access {
e.codomainOnce.Do(func() {
codomain := common.MappedSliceWithNoDuplicates(
e.Type.Relations,
Expand Down Expand Up @@ -458,6 +464,9 @@ func (a PrimitiveAccess) Equal(other Access) bool {
}

func (a PrimitiveAccess) PermitsAccess(otherAccess Access) bool {
if a == InaccessibleAccess {
return otherAccess == InaccessibleAccess
}
if otherPrimitive, ok := otherAccess.(PrimitiveAccess); ok {
return ast.PrimitiveAccess(a) >= ast.PrimitiveAccess(otherPrimitive)
}
Expand Down
10 changes: 9 additions & 1 deletion runtime/sema/check_member_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,12 @@ func (checker *Checker) mapAccess(
// 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.
// However, if the map is or includes the `Identity` map, the theoretical codomain of that map is infinite, and so no reference can
// possibly be authorized enough to write to it
if checker.inAssignment {
if mappedAccess.Type.IncludesIdentity {
return true, InaccessibleAccess
}
return true, mappedAccess.Codomain()
}
return true, grantedAccess
Expand All @@ -492,7 +497,10 @@ func (checker *Checker) mapAccess(
return checker.mapAccess(mappedAccess, ty.Type, resultingType, accessRange)

default:
if mappedAccess.Type == IdentityType {
if mappedAccess.Type.IncludesIdentity {
if checker.inAssignment {
return true, InaccessibleAccess
}
access := AllSupportedEntitlements(resultingType)
if access != nil {
return true, access
Expand Down
13 changes: 9 additions & 4 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6946,6 +6946,7 @@ var _ ValueIndexableType = &ReferenceType{}
var _ TypeIndexableType = &ReferenceType{}

var UnauthorizedAccess Access = PrimitiveAccess(ast.AccessAll)
var InaccessibleAccess Access = PrimitiveAccess(ast.AccessNone)

func NewReferenceType(
memoryGauge common.MemoryGauge,
Expand Down Expand Up @@ -9263,10 +9264,14 @@ func NewEntitlementRelation(
}

type EntitlementMapType struct {
Location common.Location
containerType Type
Identifier string
Relations []EntitlementRelation
Location common.Location
containerType Type
Identifier string
Relations []EntitlementRelation

// Whether this map type includes the special identity relation,
// which maps every input to itself. The `Identity` mapping itself
// is defined as the empty map type that includes the identity relation
IncludesIdentity bool
resolveInclusions sync.Once
}
Expand Down
Loading

0 comments on commit 24b0b11

Please sign in to comment.