From c046e280e29b9964b5a4a31e2479eb1707c648e6 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 1 Aug 2023 14:03:46 -0400 Subject: [PATCH 1/4] include interface conformances when computing supported entitlements for interfaces --- runtime/sema/type.go | 4 +++- runtime/tests/checker/entitlements_test.go | 27 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 2e665098dc..28d3b34157 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4932,7 +4932,9 @@ func (t *InterfaceType) SupportedEntitlements() (set *EntitlementOrderedSet) { }) } }) - // TODO: include inherited entitlements + t.EffectiveInterfaceConformanceSet().ForEach(func(it *InterfaceType) { + set.SetAll(it.SupportedEntitlements()) + }) t.supportedEntitlements = set return set diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index aee60d9740..17dd09ede7 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4772,6 +4772,33 @@ func TestCheckEntitlementConditions(t *testing.T) { assert.NoError(t, err) }) + + t.Run("result value inherited interface entitlement resource", func(t *testing.T) { + t.Parallel() + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + resource interface I { + access(X) view fun foo(): Bool { + return true + } + } + resource interface J: I { + access(Y) view fun bar(): Bool { + return true + } + } + fun bar(r: @{J}): @{J} { + post { + result.foo(): "" + result.bar(): "" + } + return <-r + } + `) + + assert.NoError(t, err) + }) } func TestCheckEntitledWriteAndMutateNotAllowed(t *testing.T) { From a42e1dd4be43123f0eb8b069b1664af82f3c88a0 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 3 Aug 2023 09:32:35 -0400 Subject: [PATCH 2/4] compute entitled types --- runtime/sema/type.go | 37 +++++++++++++++++++++++++++++++++++++ runtime/sema/type_test.go | 7 +++++++ 2 files changed, 44 insertions(+) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 2e665098dc..90562f4778 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -7655,6 +7655,43 @@ func (t *EntitlementMapType) Resolve(_ *TypeParameterTypeOrderedMap) Type { return t } +// Converts its input to an entitled type according to the following rules: +// * `ConvertToEntitledType(&T) ---> auth(Entitlements(T)) &T` +// * `ConvertToEntitledType(Capability) ---> Capability` +// * `ConvertToEntitledType(T) ---> T` +// where Entitlements(I) is defined as the result of T.SupportedEntitlements() +func ConvertToEntitledType(memoryGauge common.MemoryGauge, t Type) Type { + switch t := t.(type) { + case *ReferenceType: + switch t.Authorization { + case UnauthorizedAccess: + innerType := ConvertToEntitledType(memoryGauge, t.Type) + auth := UnauthorizedAccess + if entitlementSupportingType, ok := innerType.(EntitlementSupportingType); ok { + supportedEntitlements := entitlementSupportingType.SupportedEntitlements() + if supportedEntitlements.Len() > 0 { + auth = EntitlementSetAccess{ + SetKind: Conjunction, + Entitlements: supportedEntitlements, + } + } + } + return NewReferenceType( + memoryGauge, + innerType, + auth, + ) + // type is already entitled + default: + return t + } + case *CapabilityType: + return NewCapabilityType(memoryGauge, ConvertToEntitledType(memoryGauge, t.BorrowType)) + default: + return t + } +} + var NativeCompositeTypes = map[string]*CompositeType{} func init() { diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 2552d49fca..5c6bc5b4fd 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2021,3 +2021,10 @@ func TestMapType(t *testing.T) { require.True(t, outputFunction.Parameters[0].TypeAnnotation.Type.(*GenericType).TypeParameter == outputFunction.TypeParameters[0]) }) } + +func TestConvertToEntitledType(t *testing.T) { + + t.Parallel() + + entitlementE := NewEntitlementType() +} From dbc80387a26a197484a982d30ad313d015e1a4c5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 3 Aug 2023 11:28:20 -0400 Subject: [PATCH 3/4] tests for convert to entitled type --- runtime/sema/type.go | 3 + runtime/sema/type_test.go | 327 +++++++++++++++++++++++++++++++++++++- 2 files changed, 329 insertions(+), 1 deletion(-) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index c8efc5abf4..fe1ecc81ec 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -7660,6 +7660,7 @@ func (t *EntitlementMapType) Resolve(_ *TypeParameterTypeOrderedMap) Type { // Converts its input to an entitled type according to the following rules: // * `ConvertToEntitledType(&T) ---> auth(Entitlements(T)) &T` // * `ConvertToEntitledType(Capability) ---> Capability` +// * `ConvertToEntitledType(T?) ---> ConvertToEntitledType(T)? // * `ConvertToEntitledType(T) ---> T` // where Entitlements(I) is defined as the result of T.SupportedEntitlements() func ConvertToEntitledType(memoryGauge common.MemoryGauge, t Type) Type { @@ -7687,6 +7688,8 @@ func ConvertToEntitledType(memoryGauge common.MemoryGauge, t Type) Type { default: return t } + case *OptionalType: + return NewOptionalType(memoryGauge, ConvertToEntitledType(memoryGauge, t.Type)) case *CapabilityType: return NewCapabilityType(memoryGauge, ConvertToEntitledType(memoryGauge, t.BorrowType)) default: diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 5c6bc5b4fd..549a8ccd41 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2026,5 +2026,330 @@ func TestConvertToEntitledType(t *testing.T) { t.Parallel() - entitlementE := NewEntitlementType() + testLocation := common.StringLocation("test") + + entitlementE := NewEntitlementType(nil, testLocation, "E") + entitlementF := NewEntitlementType(nil, testLocation, "F") + entitlementG := NewEntitlementType(nil, testLocation, "G") + + eAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementE}, Conjunction) + fAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementF}, Conjunction) + eOrFAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementE, entitlementF}, Disjunction) + eAndFAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementE, entitlementF}, Conjunction) + eAndGAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementE, entitlementG}, Conjunction) + eFAndGAccess := NewEntitlementSetAccess([]*EntitlementType{entitlementE, entitlementF, entitlementG}, Conjunction) + + mapM := NewEntitlementMapType(nil, testLocation, "M") + mapM.Relations = []EntitlementRelation{ + { + Input: entitlementE, + Output: entitlementF, + }, + { + Input: entitlementF, + Output: entitlementG, + }, + } + mapAccess := NewEntitlementMapAccess(mapM) + + compositeStructWithOnlyE := &CompositeType{ + Location: testLocation, + Identifier: "S", + Kind: common.CompositeKindStructure, + Members: &StringMemberOrderedMap{}, + } + compositeStructWithOnlyE.Members.Set( + "foo", + NewFieldMember(nil, compositeStructWithOnlyE, eAccess, ast.VariableKindConstant, "foo", IntType, ""), + ) + + compositeResourceWithOnlyF := &CompositeType{ + Location: testLocation, + Identifier: "R", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + compositeResourceWithOnlyF.Members.Set( + "bar", + NewFieldMember(nil, compositeResourceWithOnlyF, fAccess, ast.VariableKindConstant, "bar", IntType, ""), + ) + compositeResourceWithOnlyF.Members.Set( + "baz", + NewFieldMember(nil, compositeResourceWithOnlyF, fAccess, ast.VariableKindConstant, "baz", compositeStructWithOnlyE, ""), + ) + + compositeResourceWithEOrF := &CompositeType{ + Location: testLocation, + Identifier: "R", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + compositeResourceWithEOrF.Members.Set( + "qux", + NewFieldMember(nil, compositeResourceWithEOrF, eOrFAccess, ast.VariableKindConstant, "qux", IntType, ""), + ) + + compositeTwoFields := &CompositeType{ + Location: testLocation, + Identifier: "S", + Kind: common.CompositeKindStructure, + Members: &StringMemberOrderedMap{}, + } + compositeTwoFields.Members.Set( + "foo", + NewFieldMember(nil, compositeTwoFields, eAccess, ast.VariableKindConstant, "foo", IntType, ""), + ) + compositeTwoFields.Members.Set( + "bar", + NewFieldMember(nil, compositeTwoFields, fAccess, ast.VariableKindConstant, "bar", IntType, ""), + ) + + interfaceTypeWithEAndG := &InterfaceType{ + Location: testLocation, + Identifier: "I", + CompositeKind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + interfaceTypeWithEAndG.Members.Set( + "foo", + NewFunctionMember(nil, interfaceTypeWithEAndG, eAndGAccess, "foo", &FunctionType{}, ""), + ) + + interfaceTypeInheriting := &InterfaceType{ + Location: testLocation, + Identifier: "J", + CompositeKind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + ExplicitInterfaceConformances: []*InterfaceType{interfaceTypeWithEAndG}, + } + + compositeTypeInheriting := &CompositeType{ + Location: testLocation, + Identifier: "RI", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + ExplicitInterfaceConformances: []*InterfaceType{interfaceTypeInheriting}, + } + + compositeTypeWithMap := &CompositeType{ + Location: testLocation, + Identifier: "RI", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + compositeTypeWithMap.Members.Set( + "foo", + NewFunctionMember(nil, compositeTypeWithMap, mapAccess, "foo", &FunctionType{}, ""), + ) + + interfaceTypeWithMap := &InterfaceType{ + Location: testLocation, + Identifier: "RI", + CompositeKind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + interfaceTypeWithMap.Members.Set( + "foo", + NewFunctionMember(nil, interfaceTypeWithMap, mapAccess, "foo", &FunctionType{}, ""), + ) + + compositeTypeWithCapField := &CompositeType{ + Location: testLocation, + Identifier: "RI", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + compositeTypeWithCapField.Members.Set( + "foo", + NewFieldMember( + nil, compositeTypeWithCapField, UnauthorizedAccess, ast.VariableKindConstant, "foo", + NewCapabilityType(nil, + NewReferenceType(nil, interfaceTypeInheriting, UnauthorizedAccess), + ), + "", + ), + ) + + interfaceTypeWithCapField := &InterfaceType{ + Location: testLocation, + Identifier: "RI", + CompositeKind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + } + interfaceTypeWithCapField.Members.Set( + "foo", + NewFieldMember( + nil, interfaceTypeWithCapField, UnauthorizedAccess, ast.VariableKindConstant, "foo", + NewCapabilityType(nil, + NewReferenceType(nil, interfaceTypeInheriting, UnauthorizedAccess), + ), + "", + ), + ) + + interfaceTypeInheritingCapField := &InterfaceType{ + Location: testLocation, + Identifier: "J", + CompositeKind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + ExplicitInterfaceConformances: []*InterfaceType{interfaceTypeWithCapField}, + } + + compositeTypeInheritingCapField := &CompositeType{ + Location: testLocation, + Identifier: "RI", + Kind: common.CompositeKindResource, + Members: &StringMemberOrderedMap{}, + ExplicitInterfaceConformances: []*InterfaceType{interfaceTypeInheritingCapField}, + } + + tests := []struct { + Input Type + Output Type + Name string + }{ + { + Input: NewReferenceType(nil, IntType, UnauthorizedAccess), + Output: NewReferenceType(nil, IntType, UnauthorizedAccess), + Name: "int", + }, + { + Input: NewReferenceType(nil, &FunctionType{}, UnauthorizedAccess), + Output: NewReferenceType(nil, &FunctionType{}, UnauthorizedAccess), + Name: "function", + }, + { + Input: NewReferenceType(nil, compositeStructWithOnlyE, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeStructWithOnlyE, eAccess), + Name: "composite E", + }, + { + Input: NewReferenceType(nil, compositeResourceWithOnlyF, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeResourceWithOnlyF, fAccess), + Name: "composite F", + }, + { + Input: NewReferenceType(nil, compositeResourceWithEOrF, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeResourceWithEOrF, eAndFAccess), + Name: "composite E or F", + }, + { + Input: NewReferenceType(nil, compositeTwoFields, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeTwoFields, eAndFAccess), + Name: "composite E and F", + }, + { + Input: NewReferenceType(nil, interfaceTypeWithEAndG, UnauthorizedAccess), + Output: NewReferenceType(nil, interfaceTypeWithEAndG, eAndGAccess), + Name: "interface E and G", + }, + { + Input: NewReferenceType(nil, interfaceTypeInheriting, UnauthorizedAccess), + Output: NewReferenceType(nil, interfaceTypeInheriting, eAndGAccess), + Name: "interface inheritance", + }, + { + Input: NewReferenceType(nil, compositeTypeInheriting, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeTypeInheriting, eAndGAccess), + Name: "composite inheritance", + }, + { + Input: NewReferenceType(nil, compositeTypeWithMap, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeTypeWithMap, eAndFAccess), + Name: "composite map", + }, + { + Input: NewReferenceType(nil, interfaceTypeWithMap, UnauthorizedAccess), + Output: NewReferenceType(nil, interfaceTypeWithMap, eAndFAccess), + Name: "interface map", + }, + { + Input: NewReferenceType(nil, NewCapabilityType(nil, NewReferenceType(nil, compositeTypeWithMap, UnauthorizedAccess)), UnauthorizedAccess), + Output: NewReferenceType(nil, NewCapabilityType(nil, NewReferenceType(nil, compositeTypeWithMap, eAndFAccess)), UnauthorizedAccess), + Name: "reference to capability", + }, + { + Input: NewReferenceType(nil, NewIntersectionType(nil, []*InterfaceType{interfaceTypeInheriting, interfaceTypeWithMap}), UnauthorizedAccess), + Output: NewReferenceType(nil, NewIntersectionType(nil, []*InterfaceType{interfaceTypeInheriting, interfaceTypeWithMap}), eFAndGAccess), + Name: "intersection", + }, + // no change + { + Input: NewReferenceType(nil, compositeTypeWithCapField, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeTypeWithCapField, UnauthorizedAccess), + Name: "composite with capability field", + }, + // no change + { + Input: NewReferenceType(nil, interfaceTypeWithCapField, UnauthorizedAccess), + Output: NewReferenceType(nil, interfaceTypeWithCapField, UnauthorizedAccess), + Name: "interface with capability field", + }, + // no change + { + Input: NewReferenceType(nil, compositeTypeInheritingCapField, UnauthorizedAccess), + Output: NewReferenceType(nil, compositeTypeInheritingCapField, UnauthorizedAccess), + Name: "composite inheriting capability field", + }, + // no change + { + Input: NewReferenceType(nil, interfaceTypeInheritingCapField, UnauthorizedAccess), + Output: NewReferenceType(nil, interfaceTypeInheritingCapField, UnauthorizedAccess), + Name: "interface inheriting capability field", + }, + } + + // create capability versions of all the existing tests + for _, test := range tests { + var capabilityTest struct { + Input Type + Output Type + Name string + } + capabilityTest.Input = NewCapabilityType(nil, test.Input) + capabilityTest.Output = NewCapabilityType(nil, test.Output) + capabilityTest.Name = "capability " + test.Name + + tests = append(tests, capabilityTest) + } + + // create optional versions of all the existing tests + for _, test := range tests { + var optionalTest struct { + Input Type + Output Type + Name string + } + optionalTest.Input = NewOptionalType(nil, test.Input) + optionalTest.Output = NewOptionalType(nil, test.Output) + optionalTest.Name = "optional " + test.Name + + tests = append(tests, optionalTest) + } + + var compareTypesRecursively func(t *testing.T, expected Type, actual Type) + compareTypesRecursively = func(t *testing.T, expected Type, actual Type) { + require.IsType(t, expected, actual) + + switch expected := expected.(type) { + case *ReferenceType: + actual := actual.(*ReferenceType) + require.IsType(t, expected.Authorization, actual.Authorization) + require.True(t, expected.Authorization.Equal(actual.Authorization)) + compareTypesRecursively(t, expected.Type, actual.Type) + case *OptionalType: + actual := actual.(*OptionalType) + compareTypesRecursively(t, expected.Type, actual.Type) + case *CapabilityType: + actual := actual.(*CapabilityType) + compareTypesRecursively(t, expected.BorrowType, actual.BorrowType) + } + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + compareTypesRecursively(t, ConvertToEntitledType(nil, test.Input), test.Output) + }) + } + } From e5f4d470d99fadd10c58bce7ebd71447f6543f83 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 3 Aug 2023 11:33:39 -0400 Subject: [PATCH 4/4] add todo --- runtime/sema/type_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 549a8ccd41..2cab0cd633 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2297,6 +2297,7 @@ func TestConvertToEntitledType(t *testing.T) { Output: NewReferenceType(nil, interfaceTypeInheritingCapField, UnauthorizedAccess), Name: "interface inheriting capability field", }, + // TODO: add tests for array and dictionary entitlements once the mutability changes are merged } // create capability versions of all the existing tests