From 54ba25364699837834070ebef5864d41da77c750 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 12:59:26 -0400 Subject: [PATCH 01/52] remove parsing support for requiring entitlements --- runtime/ast/attachment.go | 59 ++----- runtime/ast/attachment_test.go | 84 +-------- runtime/interpreter/interpreter_expression.go | 8 - runtime/interpreter/value.go | 8 - runtime/parser/declaration.go | 49 ------ runtime/parser/declaration_test.go | 164 +----------------- runtime/sema/check_attach_expression.go | 13 -- runtime/sema/check_composite_declaration.go | 27 --- runtime/sema/type.go | 1 - 9 files changed, 20 insertions(+), 393 deletions(-) diff --git a/runtime/ast/attachment.go b/runtime/ast/attachment.go index 397e1fa181..450c075493 100644 --- a/runtime/ast/attachment.go +++ b/runtime/ast/attachment.go @@ -29,13 +29,12 @@ import ( // AttachmentDeclaration type AttachmentDeclaration struct { - Access Access - Identifier Identifier - BaseType *NominalType - Conformances []*NominalType - RequiredEntitlements []*NominalType - Members *Members - DocString string + Access Access + Identifier Identifier + BaseType *NominalType + Conformances []*NominalType + Members *Members + DocString string Range } @@ -50,7 +49,6 @@ func NewAttachmentDeclaration( identifier Identifier, baseType *NominalType, conformances []*NominalType, - requiredEntitlements []*NominalType, members *Members, docString string, declarationRange Range, @@ -58,14 +56,13 @@ func NewAttachmentDeclaration( common.UseMemory(memoryGauge, common.AttachmentDeclarationMemoryUsage) return &AttachmentDeclaration{ - Access: access, - Identifier: identifier, - BaseType: baseType, - Conformances: conformances, - RequiredEntitlements: requiredEntitlements, - Members: members, - DocString: docString, - Range: declarationRange, + Access: access, + Identifier: identifier, + BaseType: baseType, + Conformances: conformances, + Members: members, + DocString: docString, + Range: declarationRange, } } @@ -113,10 +110,6 @@ func (d *AttachmentDeclaration) ConformanceList() []*NominalType { return d.Conformances } -func (d *AttachmentDeclaration) RequiredEntitlementsToAttach() []*NominalType { - return d.RequiredEntitlements -} - const attachmentStatementDoc = prettier.Text("attachment") const attachmentStatementForDoc = prettier.Text("for") const attachmentConformancesSeparatorDoc = prettier.Text(":") @@ -151,31 +144,7 @@ func (d *AttachmentDeclaration) Doc() prettier.Doc { ) var membersDoc prettier.Concat - if d.RequiredEntitlements != nil && len(d.RequiredEntitlements) > 0 { - membersDoc = append(membersDoc, membersStartDoc) - for _, entitlement := range d.RequiredEntitlements { - var entitlementRequiredDoc = prettier.Indent{ - Doc: prettier.Concat{ - attachmentRequireDoc, - prettier.Space, - attachmentEntitlementDoc, - prettier.Space, - entitlement.Doc(), - }, - } - membersDoc = append( - membersDoc, - prettier.HardLine{}, - entitlementRequiredDoc, - ) - } - if len(d.Members.declarations) > 0 { - membersDoc = append(membersDoc, prettier.HardLine{}, d.Members.docWithNoBraces()) - } - membersDoc = append(membersDoc, prettier.HardLine{}, membersEndDoc) - } else { - membersDoc = append(membersDoc, prettier.Line{}, d.Members.Doc()) - } + membersDoc = append(membersDoc, prettier.Line{}, d.Members.Doc()) if len(d.Conformances) > 0 { conformancesDoc := prettier.Concat{ diff --git a/runtime/ast/attachment_test.go b/runtime/ast/attachment_test.go index 65d6faf50b..c42a27afae 100644 --- a/runtime/ast/attachment_test.go +++ b/runtime/ast/attachment_test.go @@ -56,22 +56,6 @@ func TestAttachmentDeclaration_MarshallJSON(t *testing.T) { ), }, }, - RequiredEntitlements: []*NominalType{ - { - Identifier: NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - { - Identifier: NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - }, Members: NewMembers(nil, []Declaration{}), DocString: "test", Range: Range{ @@ -118,28 +102,6 @@ func TestAttachmentDeclaration_MarshallJSON(t *testing.T) { "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 3, "Line": 2, "Column": 5} } - ], - "RequiredEntitlements": [ - { - "Type": "NominalType", - "Identifier": { - "Identifier": "X", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - { - "Type": "NominalType", - "Identifier": { - "Identifier": "Y", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - } ], "Members": { "Declarations": [] @@ -179,22 +141,6 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { ), }, }, - RequiredEntitlements: []*NominalType{ - { - Identifier: NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - { - Identifier: NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - }, Members: NewMembers(nil, []Declaration{}), DocString: "test", Range: Range{ @@ -225,29 +171,8 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { Doc: prettier.Concat{ prettier.Line{}, prettier.Concat{ - prettier.Text("{"), - prettier.HardLine{}, - prettier.Indent{ - Doc: prettier.Concat{ - prettier.Text("require"), - prettier.Text(" "), - prettier.Text("entitlement"), - prettier.Text(" "), - prettier.Text("X"), - }, - }, - prettier.HardLine{}, - prettier.Indent{ - Doc: prettier.Concat{ - prettier.Text("require"), - prettier.Text(" "), - prettier.Text("entitlement"), - prettier.Text(" "), - prettier.Text("Y"), - }, - }, - prettier.HardLine{}, - prettier.Text("}"), + prettier.Line{}, + prettier.Text("{}"), }, }, }, @@ -260,10 +185,7 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { require.Equal(t, `access(all) -attachment Foo for Bar: Baz { -require entitlement X -require entitlement Y -}`, +attachment Foo for Bar: Baz {}`, decl.String(), ) } diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 171695b532..179f1bb800 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1411,14 +1411,6 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta var auth Authorization = UnauthorizedAccess attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess := sema.EntitlementSetAccess{ - SetKind: sema.Conjunction, - Entitlements: attachmentType.RequiredEntitlements, - } - auth = ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) - } - var baseValue Value = NewEphemeralReferenceValue( interpreter, auth, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 8e46111afa..d52a2249d7 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17842,14 +17842,6 @@ func attachmentBaseAuthorization( attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess - attachmentType := interpreter.MustSemaTypeOfValue(attachment).(*sema.CompositeType) - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess := sema.EntitlementSetAccess{ - SetKind: sema.Conjunction, - Entitlements: attachmentType.RequiredEntitlements, - } - auth = ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) - } return auth } diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index 9f067dd9f0..20ae5343ef 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -1342,47 +1342,6 @@ func parseCompositeOrInterfaceDeclaration( } } -func parseRequiredEntitlement(p *parser) (*ast.NominalType, error) { - if !p.isToken(p.current, lexer.TokenIdentifier, KeywordRequire) { - return nil, p.syntaxError( - "expected 'require', got %s", - p.current.Type, - ) - } - - // skip the `require` keyword - p.nextSemanticToken() - - if !p.isToken(p.current, lexer.TokenIdentifier, KeywordEntitlement) { - return nil, p.syntaxError( - "expected 'entitlement', got %s", - p.current.Type, - ) - } - - // skip the `entitlement` keyword - p.nextSemanticToken() - - return rejectAccessKeywords(p, func() (*ast.NominalType, error) { - return parseNominalType(p, lowestBindingPower) - }) -} - -func parseRequiredEntitlements(p *parser) ([]*ast.NominalType, error) { - var requiredEntitlements []*ast.NominalType - - for p.isToken(p.current, lexer.TokenIdentifier, KeywordRequire) { - requiredEntitlement, err := parseRequiredEntitlement(p) - if err != nil { - return nil, err - } - requiredEntitlements = append(requiredEntitlements, requiredEntitlement) - p.skipSpaceAndComments() - } - - return requiredEntitlements, nil -} - func parseAttachmentDeclaration( p *parser, access ast.Access, @@ -1448,13 +1407,6 @@ func parseAttachmentDeclaration( p.skipSpaceAndComments() - requiredEntitlements, err := parseRequiredEntitlements(p) - if err != nil { - return nil, err - } - - p.skipSpaceAndComments() - members, err := parseMembersAndNestedDeclarations(p, lexer.TokenBraceClose) if err != nil { return nil, err @@ -1479,7 +1431,6 @@ func parseAttachmentDeclaration( identifier, baseNominalType, conformances, - requiredEntitlements, members, docString, declarationRange, diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index d0f00041cb..189977503f 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -4098,176 +4098,18 @@ func TestParseAttachmentDeclaration(t *testing.T) { ) }) - t.Run("required entitlements", func(t *testing.T) { - - t.Parallel() - - result, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement X - require entitlement Y - destroy() {} - }`) - require.Empty(t, errs) - - utils.AssertEqualWithDiff(t, - []ast.Declaration{ - &ast.AttachmentDeclaration{ - Access: ast.AccessAll, - Identifier: ast.Identifier{ - Identifier: "E", - Pos: ast.Position{ - Offset: 23, - Line: 1, - Column: 23, - }, - }, - BaseType: &ast.NominalType{ - Identifier: ast.Identifier{ - Identifier: "S", - Pos: ast.Position{ - Offset: 29, - Line: 1, - Column: 29, - }, - }, - }, - RequiredEntitlements: []*ast.NominalType{ - { - Identifier: ast.Identifier{ - Identifier: "X", - Pos: ast.Position{ - Offset: 56, - Line: 2, - Column: 23, - }, - }, - }, - { - Identifier: ast.Identifier{ - Identifier: "Y", - Pos: ast.Position{ - Offset: 81, - Line: 3, - Column: 23, - }, - }, - }, - }, - Members: ast.NewUnmeteredMembers( - []ast.Declaration{ - &ast.SpecialFunctionDeclaration{ - FunctionDeclaration: &ast.FunctionDeclaration{ - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 93, - Line: 4, - Column: 10, - }, - EndPos: ast.Position{ - Offset: 94, - Line: 4, - Column: 11, - }, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 96, - Line: 4, - Column: 13, - }, - EndPos: ast.Position{ - Offset: 97, - Line: 4, - Column: 14, - }, - }, - }, - }, - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{ - Offset: 86, - Line: 4, - Column: 3, - }, - }, - StartPos: ast.Position{ - Offset: 86, - Line: 4, - Column: 3, - }, - Access: ast.AccessNotSpecified, - }, - Kind: 0xe, - }, - }, - ), - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 0, - Line: 1, - Column: 0, - }, - EndPos: ast.Position{ - Offset: 101, - Line: 5, - Column: 2, - }, - }, - }, - }, - result, - ) - }) - t.Run("required entitlements error no identifier", func(t *testing.T) { t.Parallel() _, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement - destroy() {} - }`) - utils.AssertEqualWithDiff(t, []error{ - &SyntaxError{ - Pos: ast.Position{Line: 3, Column: 10, Offset: 67}, - Message: "unexpected '('", - }, - }, errs) - }) - - t.Run("required entitlements error no entitlement", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseDeclarations(`access(all) attachment E for S { - require X - destroy() {} - }`) - utils.AssertEqualWithDiff(t, []error{ - &SyntaxError{ - Pos: ast.Position{Line: 2, Column: 11, Offset: 44}, - Message: "expected 'entitlement', got identifier", - }, - }, errs) - }) - - t.Run("required entitlements error non-nominal type", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement [X] + require entitlement X destroy() {} }`) utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ - Pos: ast.Position{Line: 2, Column: 26, Offset: 59}, - Message: "unexpected non-nominal type: [X]", + Pos: ast.Position{Line: 2, Column: 3, Offset: 36}, + Message: "unexpected identifier", }, }, errs) }) diff --git a/runtime/sema/check_attach_expression.go b/runtime/sema/check_attach_expression.go index 9dd401c8bc..55e13a3669 100644 --- a/runtime/sema/check_attach_expression.go +++ b/runtime/sema/check_attach_expression.go @@ -127,18 +127,5 @@ func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) }) } - // if the attachment requires entitlements, check that they are provided as requested - if attachmentCompositeType.RequiredEntitlements != nil { - attachmentCompositeType.RequiredEntitlements.Foreach(func(key *EntitlementType, _ struct{}) { - if !providedEntitlements.Contains(key) { - checker.report(&RequiredEntitlementNotProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, expression), - AttachmentType: attachmentCompositeType, - RequiredEntitlement: key, - }) - } - }) - } - return baseType } diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 574d6d02c9..ef12c70bd3 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -628,27 +628,6 @@ func (checker *Checker) declareAttachmentType(declaration *ast.AttachmentDeclara composite.AttachmentEntitlementAccess = attachmentAccess } - // add all the required entitlements to a set for this attachment - requiredEntitlements := orderedmap.New[EntitlementOrderedSet](len(declaration.RequiredEntitlements)) - for _, entitlement := range declaration.RequiredEntitlements { - nominalType := checker.convertNominalType(entitlement) - if entitlementType, isEntitlement := nominalType.(*EntitlementType); isEntitlement { - _, present := requiredEntitlements.Set(entitlementType, struct{}{}) - if present { - checker.report(&DuplicateEntitlementRequirementError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - Entitlement: entitlementType, - }) - } - continue - } - checker.report(&InvalidNonEntitlementRequirement{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - InvalidType: nominalType, - }) - } - composite.RequiredEntitlements = requiredEntitlements - return composite } @@ -2225,12 +2204,6 @@ func (checker *Checker) declareBaseValue(baseType Type, attachmentType *Composit // ------------------------------- // within the body of `foo`, the `base` value will be entitled to `E` but not `F`, because only `E` was required in the attachment's declaration var baseAccess Access = UnauthorizedAccess - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess = EntitlementSetAccess{ - Entitlements: attachmentType.RequiredEntitlements, - SetKind: Conjunction, - } - } base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index cf7be4bf8e..cbaf02f95f 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4293,7 +4293,6 @@ type CompositeType struct { // Alas, this is Go, so for now these fields are only non-nil when Kind is CompositeKindAttachment baseType Type baseTypeDocString string - RequiredEntitlements *EntitlementOrderedSet AttachmentEntitlementAccess *EntitlementMapAccess cachedIdentifiers *struct { From 394565511361061b803eb9c14134a82f911ca302 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 13:24:26 -0400 Subject: [PATCH 02/52] remove support for providing entitlements during attach --- runtime/ast/attachment.go | 33 +- runtime/ast/attachment_test.go | 73 +--- runtime/contract_update_validation_test.go | 135 ------- runtime/parser/expression.go | 32 +- runtime/parser/expression_test.go | 87 +--- runtime/parser/keyword.go | 2 - runtime/sema/check_attach_expression.go | 21 - runtime/sema/errors.go | 88 ----- runtime/stdlib/contract_update_validation.go | 62 --- runtime/tests/checker/entitlements_test.go | 394 ------------------- 10 files changed, 13 insertions(+), 914 deletions(-) diff --git a/runtime/ast/attachment.go b/runtime/ast/attachment.go index 450c075493..c16005db4d 100644 --- a/runtime/ast/attachment.go +++ b/runtime/ast/attachment.go @@ -113,8 +113,6 @@ func (d *AttachmentDeclaration) ConformanceList() []*NominalType { const attachmentStatementDoc = prettier.Text("attachment") const attachmentStatementForDoc = prettier.Text("for") const attachmentConformancesSeparatorDoc = prettier.Text(":") -const attachmentEntitlementDoc = prettier.Text("entitlement") -const attachmentRequireDoc = prettier.Text("require") var attachmentConformanceSeparatorDoc prettier.Doc = prettier.Concat{ prettier.Text(","), @@ -213,10 +211,9 @@ func (d *AttachmentDeclaration) String() string { // AttachExpression type AttachExpression struct { - Base Expression - Attachment *InvocationExpression - Entitlements []*NominalType - StartPos Position `json:"-"` + Base Expression + Attachment *InvocationExpression + StartPos Position `json:"-"` } var _ Element = &AttachExpression{} @@ -239,16 +236,14 @@ func NewAttachExpression( gauge common.MemoryGauge, base Expression, attachment *InvocationExpression, - entitlements []*NominalType, startPos Position, ) *AttachExpression { common.UseMemory(gauge, common.AttachExpressionMemoryUsage) return &AttachExpression{ - Base: base, - Attachment: attachment, - Entitlements: entitlements, - StartPos: startPos, + Base: base, + Attachment: attachment, + StartPos: startPos, } } @@ -258,8 +253,6 @@ func (e *AttachExpression) String() string { const attachExpressionDoc = prettier.Text("attach") const attachExpressionToDoc = prettier.Text("to") -const attachExpressionWithDoc = prettier.Text("with") -const attachExpressionCommaDoc = prettier.Text(",") func (e *AttachExpression) Doc() prettier.Doc { var doc prettier.Concat @@ -274,17 +267,6 @@ func (e *AttachExpression) Doc() prettier.Doc { prettier.Space, e.Base.Doc(), ) - if len(e.Entitlements) > 0 { - entitlementsLen := len(e.Entitlements) - doc = append(doc, prettier.Space, attachExpressionWithDoc, prettier.Space, openParenthesisDoc) - for i, entitlement := range e.Entitlements { - doc = append(doc, entitlement.Doc()) - if i < entitlementsLen-1 { - doc = append(doc, attachExpressionCommaDoc, prettier.Space) - } - } - doc = append(doc, closeParenthesisDoc) - } return doc } @@ -293,9 +275,6 @@ func (e *AttachExpression) StartPosition() Position { } func (e *AttachExpression) EndPosition(memoryGauge common.MemoryGauge) Position { - if len(e.Entitlements) > 0 { - return e.Entitlements[len(e.Entitlements)-1].EndPosition(memoryGauge) - } return e.Base.EndPosition(memoryGauge) } diff --git a/runtime/ast/attachment_test.go b/runtime/ast/attachment_test.go index c42a27afae..35d27e119a 100644 --- a/runtime/ast/attachment_test.go +++ b/runtime/ast/attachment_test.go @@ -218,24 +218,6 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { Position{Offset: 1, Line: 2, Column: 3}, Position{Offset: 1, Line: 2, Column: 3}, ), - Entitlements: []*NominalType{ - NewNominalType(nil, - NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - NewNominalType(nil, - NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - }, StartPos: Position{Offset: 1, Line: 2, Column: 3}, } @@ -248,7 +230,7 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { { "Type": "AttachExpression", "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 3, "Line": 2, "Column": 5}, "Base": { "Type": "IdentifierExpression", "Identifier": { @@ -276,29 +258,7 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "ArgumentsStartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "Entitlements": [ - { - "Type": "NominalType", - "Identifier": { - "Identifier": "X", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - { - "Type": "NominalType", - "Identifier": { - "Identifier": "Y", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - } - ] + } } `, string(actual), @@ -333,24 +293,6 @@ func TestAttachExpression_Doc(t *testing.T) { Position{Offset: 1, Line: 2, Column: 3}, Position{Offset: 1, Line: 2, Column: 3}, ), - Entitlements: []*NominalType{ - NewNominalType(nil, - NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - NewNominalType(nil, - NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - }, StartPos: Position{Offset: 1, Line: 2, Column: 3}, } @@ -367,20 +309,11 @@ func TestAttachExpression_Doc(t *testing.T) { prettier.Text("to"), prettier.Text(" "), prettier.Text("foo"), - prettier.Text(" "), - prettier.Text("with"), - prettier.Text(" "), - prettier.Text("("), - prettier.Text("X"), - prettier.Text(","), - prettier.Text(" "), - prettier.Text("Y"), - prettier.Text(")"), }, decl.Doc(), ) - require.Equal(t, "attach bar() to foo with (X, Y)", decl.String()) + require.Equal(t, "attach bar() to foo", decl.String()) } func TestRemoveStatement_MarshallJSON(t *testing.T) { diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index 0617b62b6e..dcf8c5259a 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -1869,17 +1869,6 @@ func assertConformanceMismatchError( assert.Equal(t, erroneousDeclName, conformanceMismatchError.DeclName) } -func assertEntitlementRequirementMismatchError( - t *testing.T, - err error, - erroneousDeclName string, -) { - var entitlementMismatchError *stdlib.RequiredEntitlementMismatchError - require.ErrorAs(t, err, &entitlementMismatchError) - - assert.Equal(t, erroneousDeclName, entitlementMismatchError.DeclName) -} - func assertEnumCaseMismatchError(t *testing.T, err error, expectedEnumCase string, foundEnumCase string) { var enumMismatchError *stdlib.EnumCaseMismatchError require.ErrorAs(t, err, &enumMismatchError) @@ -2108,130 +2097,6 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { require.NoError(t, err) }) - t.Run("removing required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - require.NoError(t, err) - }) - - t.Run("reordering required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement Y - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - require.NoError(t, err) - }) - - t.Run("renaming required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - RequireError(t, err) - - cause := getSingleContractUpdateErrorCause(t, err, "Test") - - assertEntitlementRequirementMismatchError(t, cause, "Foo") - }) - - t.Run("adding required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - RequireError(t, err) - - cause := getSingleContractUpdateErrorCause(t, err, "Test") - - assertEntitlementRequirementMismatchError(t, cause, "Foo") - }) - t.Run("missing comma in parameter list of old contract", func(t *testing.T) { t.Parallel() diff --git a/runtime/parser/expression.go b/runtime/parser/expression.go index b9d1b5c8e5..9d06d9def6 100644 --- a/runtime/parser/expression.go +++ b/runtime/parser/expression.go @@ -971,37 +971,7 @@ func parseAttachExpressionRemainder(p *parser, token lexer.Token) (*ast.AttachEx p.skipSpaceAndComments() - var entitlements []*ast.NominalType - if p.isToken(p.current, lexer.TokenIdentifier, KeywordWith) { - // consume the `with` token - p.nextSemanticToken() - - _, err = p.mustOne(lexer.TokenParenOpen) - if err != nil { - return nil, err - } - - entitlements, _, err = parseNominalTypes(p, lexer.TokenParenClose, lexer.TokenComma) - for _, entitlement := range entitlements { - _, err = rejectAccessKeywords(p, func() (*ast.NominalType, error) { - return entitlement, nil - }) - if err != nil { - return nil, err - } - } - if err != nil { - return nil, err - } - - _, err = p.mustOne(lexer.TokenParenClose) - if err != nil { - return nil, err - } - p.skipSpaceAndComments() - } - - return ast.NewAttachExpression(p.memoryGauge, base, attachment, entitlements, token.StartPos), nil + return ast.NewAttachExpression(p.memoryGauge, base, attachment, token.StartPos), nil } // Invocation Expression Grammar: diff --git a/runtime/parser/expression_test.go b/runtime/parser/expression_test.go index ac382ca8bb..b3c27caa96 100644 --- a/runtime/parser/expression_test.go +++ b/runtime/parser/expression_test.go @@ -2843,93 +2843,12 @@ func TestParseAttach(t *testing.T) { t.Parallel() - result, errs := testParseExpression("attach E() to r with (X, Y)") - require.Empty(t, errs) - - utils.AssertEqualWithDiff(t, - &ast.AttachExpression{ - Base: &ast.IdentifierExpression{ - Identifier: ast.Identifier{ - Identifier: "r", - Pos: ast.Position{Line: 1, Column: 14, Offset: 14}, - }, - }, - Attachment: &ast.InvocationExpression{ - InvokedExpression: &ast.IdentifierExpression{ - Identifier: ast.Identifier{ - Identifier: "E", - Pos: ast.Position{Line: 1, Column: 7, Offset: 7}, - }, - }, - ArgumentsStartPos: ast.Position{Line: 1, Column: 8, Offset: 8}, - EndPos: ast.Position{Line: 1, Column: 9, Offset: 9}, - }, - Entitlements: []*ast.NominalType{ - ast.NewNominalType( - nil, - ast.Identifier{ - Identifier: "X", - Pos: ast.Position{Line: 1, Column: 22, Offset: 22}, - }, - nil, - ), - ast.NewNominalType( - nil, - ast.Identifier{ - Identifier: "Y", - Pos: ast.Position{Line: 1, Column: 25, Offset: 25}, - }, - nil, - ), - }, - StartPos: ast.Position{Line: 1, Column: 0, Offset: 0}, - }, - result, - ) - }) - - t.Run("with provided entitlements not closed", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with (X, Y") + _, errs := testParseExpression("attach E() to r with (X)") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ - Message: "invalid end of input, expected ')'", - Pos: ast.Position{Offset: 26, Line: 1, Column: 26}, - }, - }, - errs, - ) - }) - - t.Run("with provided entitlements extra comma", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with (X, Y,)") - utils.AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "missing type after separator", - Pos: ast.Position{Offset: 27, Line: 1, Column: 27}, - }, - }, - errs, - ) - }) - - t.Run("with provided entitlements unopened", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with X, Y)") - utils.AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "expected token '('", - Pos: ast.Position{Offset: 21, Line: 1, Column: 21}, + Message: "unexpected token: identifier", + Pos: ast.Position{Offset: 16, Line: 1, Column: 16}, }, }, errs, diff --git a/runtime/parser/keyword.go b/runtime/parser/keyword.go index 00e7dc9413..605b4e9e32 100644 --- a/runtime/parser/keyword.go +++ b/runtime/parser/keyword.go @@ -69,7 +69,6 @@ const ( keywordAttach = "attach" keywordRemove = "remove" keywordTo = "to" - KeywordWith = "with" KeywordRequire = "require" KeywordStatic = "static" KeywordNative = "native" @@ -122,7 +121,6 @@ var allKeywords = []string{ KeywordDefault, KeywordEnum, KeywordView, - KeywordWith, KeywordMapping, KeywordRequire, keywordAttach, diff --git a/runtime/sema/check_attach_expression.go b/runtime/sema/check_attach_expression.go index 55e13a3669..e2b70676cf 100644 --- a/runtime/sema/check_attach_expression.go +++ b/runtime/sema/check_attach_expression.go @@ -21,7 +21,6 @@ package sema import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/common/orderedmap" ) func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) Type { @@ -107,25 +106,5 @@ func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) checker.Elaboration.SetAttachTypes(expression, attachmentCompositeType) - // compute the set of all the entitlements provided to this attachment - providedEntitlements := orderedmap.New[EntitlementOrderedSet](len(expression.Entitlements)) - for _, entitlement := range expression.Entitlements { - nominalType := checker.convertNominalType(entitlement) - if entitlementType, isEntitlement := nominalType.(*EntitlementType); isEntitlement { - _, present := providedEntitlements.Set(entitlementType, struct{}{}) - if present { - checker.report(&DuplicateEntitlementProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - Entitlement: entitlementType, - }) - } - continue - } - checker.report(&InvalidNonEntitlementProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - InvalidType: nominalType, - }) - } - return baseType } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 5c4fd92b67..e5ebe8f4b6 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4429,94 +4429,6 @@ func (e *CyclicEntitlementMappingError) Error() string { ) } -type DuplicateEntitlementRequirementError struct { - Entitlement *EntitlementType - ast.Range -} - -var _ SemanticError = &DuplicateEntitlementRequirementError{} -var _ errors.UserError = &DuplicateEntitlementRequirementError{} - -func (*DuplicateEntitlementRequirementError) isSemanticError() {} - -func (*DuplicateEntitlementRequirementError) IsUserError() {} - -func (e *DuplicateEntitlementRequirementError) Error() string { - return fmt.Sprintf("entitlement %s is already required by this attachment", e.Entitlement.QualifiedString()) -} - -type DuplicateEntitlementProvidedError struct { - Entitlement *EntitlementType - ast.Range -} - -var _ SemanticError = &DuplicateEntitlementProvidedError{} -var _ errors.UserError = &DuplicateEntitlementProvidedError{} - -func (*DuplicateEntitlementProvidedError) isSemanticError() {} - -func (*DuplicateEntitlementProvidedError) IsUserError() {} - -func (e *DuplicateEntitlementProvidedError) Error() string { - return fmt.Sprintf("entitlement %s is already provided to this attachment", e.Entitlement.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type InvalidNonEntitlementRequirement struct { - InvalidType Type - ast.Range -} - -var _ SemanticError = &InvalidNonEntitlementRequirement{} -var _ errors.UserError = &InvalidNonEntitlementRequirement{} - -func (*InvalidNonEntitlementRequirement) isSemanticError() {} - -func (*InvalidNonEntitlementRequirement) IsUserError() {} - -func (e *InvalidNonEntitlementRequirement) Error() string { - return fmt.Sprintf("cannot use %s as an entitlement requirement", e.InvalidType.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type InvalidNonEntitlementProvidedError struct { - InvalidType Type - ast.Range -} - -var _ SemanticError = &InvalidNonEntitlementProvidedError{} -var _ errors.UserError = &InvalidNonEntitlementProvidedError{} - -func (*InvalidNonEntitlementProvidedError) isSemanticError() {} - -func (*InvalidNonEntitlementProvidedError) IsUserError() {} - -func (e *InvalidNonEntitlementProvidedError) Error() string { - return fmt.Sprintf("cannot provide %s as an entitlement to this attachment", e.InvalidType.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type RequiredEntitlementNotProvidedError struct { - RequiredEntitlement *EntitlementType - AttachmentType *CompositeType - ast.Range -} - -var _ SemanticError = &RequiredEntitlementNotProvidedError{} -var _ errors.UserError = &RequiredEntitlementNotProvidedError{} - -func (*RequiredEntitlementNotProvidedError) isSemanticError() {} - -func (*RequiredEntitlementNotProvidedError) IsUserError() {} - -func (e *RequiredEntitlementNotProvidedError) Error() string { - return fmt.Sprintf( - "attachment type `%s` requires entitlement `%s` to be provided when attaching", - e.AttachmentType.QualifiedString(), - e.RequiredEntitlement.QualifiedString(), - ) -} - // InvalidBaseTypeError type InvalidBaseTypeError struct { diff --git a/runtime/stdlib/contract_update_validation.go b/runtime/stdlib/contract_update_validation.go index 39f0d5edff..4c7502a27e 100644 --- a/runtime/stdlib/contract_update_validation.go +++ b/runtime/stdlib/contract_update_validation.go @@ -147,12 +147,6 @@ func (validator *ContractUpdateValidator) checkDeclarationUpdatability( validator.checkConformances(oldDecl, newDecl) } } - - if newDecl, ok := newDeclaration.(*ast.AttachmentDeclaration); ok { - if oldDecl, ok := oldDeclaration.(*ast.AttachmentDeclaration); ok { - validator.checkRequiredEntitlements(oldDecl, newDecl) - } - } } func (validator *ContractUpdateValidator) checkFields(oldDeclaration ast.Declaration, newDeclaration ast.Declaration) { @@ -338,47 +332,6 @@ func (validator *ContractUpdateValidator) checkEnumCases(oldDeclaration ast.Decl } } -func (validator *ContractUpdateValidator) checkRequiredEntitlements( - oldDecl *ast.AttachmentDeclaration, - newDecl *ast.AttachmentDeclaration, -) { - oldEntitlements := oldDecl.RequiredEntitlements - newEntitlements := newDecl.RequiredEntitlements - - // updates cannot add new entitlement requirements, or equivalently, - // the new entitlements must all be present in the old entitlements list - // Adding new entitlement requirements has to be prohibited because it would - // be a security vulnerability. If your attachment previously only requires X access to the base, - // people who might be okay giving an attachment X access to their resource would be willing to attach it. - // If the author could later add a requirement to the attachment declaration asking for Y access as well, - // then they would be able to access Y-entitled values on existing attached bases without ever having - // received explicit permission from the resource owners to access that entitlement. - - for _, newEntitlement := range newEntitlements { - found := false - for index, oldEntitlement := range oldEntitlements { - err := oldEntitlement.CheckEqual(newEntitlement, validator) - if err == nil { - found = true - - // Remove the matched entitlement, so we don't have to check it again. - // i.e: optimization - oldEntitlements = append(oldEntitlements[:index], oldEntitlements[index+1:]...) - break - } - } - - if !found { - validator.report(&RequiredEntitlementMismatchError{ - DeclName: newDecl.Identifier.Identifier, - Range: ast.NewUnmeteredRangeFromPositioned(newDecl.Identifier), - }) - - return - } - } -} - func (validator *ContractUpdateValidator) checkConformances( oldDecl *ast.CompositeDeclaration, newDecl *ast.CompositeDeclaration, @@ -595,21 +548,6 @@ func (e *ConformanceMismatchError) Error() string { return fmt.Sprintf("conformances does not match in `%s`", e.DeclName) } -// RequiredEntitlementMismatchError is reported during a contract update, when the required entitlements of the new attachment -// does not match the existing one. -type RequiredEntitlementMismatchError struct { - DeclName string - ast.Range -} - -var _ errors.UserError = &RequiredEntitlementMismatchError{} - -func (*RequiredEntitlementMismatchError) IsUserError() {} - -func (e *RequiredEntitlementMismatchError) Error() string { - return fmt.Sprintf("required entitlements do not match in `%s`", e.DeclName) -} - // EnumCaseMismatchError is reported during an enum update, when an updated enum case // does not match the existing enum case. type EnumCaseMismatchError struct { diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 2b6ea3360a..c7afcbd537 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -5733,400 +5733,6 @@ func TestCheckEntitledWriteAndMutateNotAllowed(t *testing.T) { }) } -func TestCheckAttachmentRequireEntitlements(t *testing.T) { - t.Parallel() - - t.Run("entitlements allowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - attachment A for AnyStruct { - require entitlement E - require entitlement F - } - `) - - assert.NoError(t, err) - }) - - t.Run("entitlement mapping disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement mapping M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("event disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - event M() - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("struct disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("struct interface disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct interface M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("resource disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - resource M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("resource interface disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - resource interface M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("attachment disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment M for AnyResource {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("enum disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - enum M: UInt8 {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("int disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment A for AnyStruct { - require entitlement E - require entitlement Int - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("duplicates disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment A for AnyStruct { - require entitlement E - require entitlement E - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.DuplicateEntitlementRequirementError{}, errs[0]) - }) -} - -func TestCheckAttachProvidedEntitlements(t *testing.T) { - t.Parallel() - - t.Run("all provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, F) - } - - `) - assert.NoError(t, err) - }) - - t.Run("extra provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement G - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, F, G) - } - - `) - assert.NoError(t, err) - }) - - t.Run("one missing", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E) - } - - `) - errs := RequireCheckerErrors(t, err, 1) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("one missing with extra provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement G - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, G) - } - - `) - errs := RequireCheckerErrors(t, err, 1) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("two missing", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("mapping provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement mapping M {} - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (M) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("int provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (UInt8) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("struct provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (S) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) -} - func TestCheckBuiltinEntitlements(t *testing.T) { t.Parallel() From 1d8e30bb003fadb5d28de049a6de7bab7fb69008 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 13:58:20 -0400 Subject: [PATCH 03/52] remove support for declaring attachments with entitlement maps --- runtime/interpreter/interpreter.go | 9 +- runtime/interpreter/value.go | 21 +--- runtime/sema/access.go | 10 ++ runtime/sema/check_composite_declaration.go | 109 ++++++-------------- runtime/sema/checker.go | 9 +- runtime/sema/errors.go | 21 ++-- runtime/sema/type.go | 30 +++--- runtime/tests/checker/entitlements_test.go | 16 +-- 8 files changed, 83 insertions(+), 142 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a4a89bf36e..e4d4baf11c 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1298,9 +1298,12 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( // Self's type in the constructor is codomain of the attachment's entitlement map, since // the constructor can only be called when in possession of the base resource // if the attachment is declared with access(all) access, then self is unauthorized - if attachmentType.AttachmentEntitlementAccess != nil { - auth = ConvertSemaAccessToStaticAuthorization(interpreter, attachmentType.AttachmentEntitlementAccess.Codomain()) - } + + auth = ConvertSemaAccessToStaticAuthorization( + interpreter, + sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction), + ) + self = NewEphemeralReferenceValue(interpreter, auth, value, attachmentType) // set the base to the implicitly provided value, and remove this implicit argument from the list diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index d52a2249d7..c989a4224b 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17825,16 +17825,8 @@ func attachmentReferenceAuthorization( attachmentType *sema.CompositeType, baseAccess sema.Access, ) (Authorization, error) { - // Map the entitlements of the accessing reference through the attachment's entitlement map to get the authorization of this reference - var attachmentReferenceAuth Authorization = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess == nil { - return attachmentReferenceAuth, nil - } - attachmentReferenceAccess, err := attachmentType.AttachmentEntitlementAccess.Image(baseAccess, func() ast.Range { return ast.EmptyRange }) - if err != nil { - return nil, err - } - return ConvertSemaAccessToStaticAuthorization(interpreter, attachmentReferenceAccess), nil + // The attachment reference has the same entitlements as the base access + return ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), nil } func attachmentBaseAuthorization( @@ -17851,12 +17843,7 @@ func attachmentBaseAndSelfValues( ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { base = v.getBaseValue() - attachmentType := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) - var attachmentReferenceAuth Authorization = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess != nil { - attachmentReferenceAuth = ConvertSemaAccessToStaticAuthorization(interpreter, attachmentType.AttachmentEntitlementAccess.Codomain()) - } // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) @@ -17932,8 +17919,8 @@ func (v *CompositeValue) GetTypeKey( ) Value { var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) - if isAttachmentType && attachmentTyp.AttachmentEntitlementAccess != nil { - access = attachmentTyp.AttachmentEntitlementAccess.Domain() + if isAttachmentType { + access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) } return v.getTypeKey(interpreter, locationRange, ty, access) } diff --git a/runtime/sema/access.go b/runtime/sema/access.go index b936f4495d..6855fa4a9b 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -72,6 +72,16 @@ func NewEntitlementSetAccess( } } +func NewEntitlementSetAccessFromSet( + set *EntitlementOrderedSet, + setKind EntitlementSetKind, +) EntitlementSetAccess { + return EntitlementSetAccess{ + Entitlements: set, + SetKind: setKind, + } +} + func (EntitlementSetAccess) isAccess() {} func (e EntitlementSetAccess) ID() TypeID { diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index ef12c70bd3..1ba8bedc5b 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -68,66 +68,37 @@ func (checker *Checker) checkAttachmentBaseType(attachmentType *CompositeType, a func (checker *Checker) checkAttachmentMembersAccess(attachmentType *CompositeType) { - // all the access modifiers for attachment members must be elements of the - // codomain of the attachment's entitlement map. This is because the codomain - // of the attachment's declared map specifies all the entitlements one can possibly - // have to that attachment, since the only way to obtain an attachment reference - // is to access it off of a base (and hence through the map). - // --------------------------------------------------- - // entitlement map M { - // E -> F - // X -> Y - // U -> V - // } - // - // access(M) attachment A for R { - // access(F) fun foo() {} - // access(Y | F) fun bar() {} - // access(V & Y) fun baz() {} - // - // access(V | Q) fun qux() {} - // } - // --------------------------------------------------- - // - // in this example, the only entitlements one can ever obtain to an &A reference are - // `F`, `Y` and `V`, and as such these are the only entitlements that may be used - // in `A`'s definition. Thus the definitions of `foo`, `bar`, and `baz` are valid, - // while the definition of `qux` is not. - var attachmentAccess Access = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess != nil { - attachmentAccess = attachmentType.AttachmentEntitlementAccess - } - - if attachmentAccess, ok := attachmentAccess.(*EntitlementMapAccess); ok { - codomain := attachmentAccess.Codomain() - attachmentType.Members.Foreach(func(_ string, member *Member) { - if memberAccess, ok := member.Access.(EntitlementSetAccess); ok { - memberAccess.Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { - if !codomain.Entitlements.Contains(entitlement) { - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - AttachmentAccessModifier: attachmentAccess, - InvalidEntitlement: entitlement, - Pos: member.Identifier.Pos, - }) - } - }) - } - }) - return + // all the access modifiers for attachment members must be valid entitlements for the base type + var supportedBaseEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + baseType := attachmentType.GetBaseType() + switch base := attachmentType.GetBaseType().(type) { + case *CompositeType: + supportedBaseEntitlements = base.SupportedEntitlements() } - // if the attachment's access is public, its members may not have entitlement access attachmentType.Members.Foreach(func(_ string, member *Member) { - if _, ok := member.Access.(PrimitiveAccess); ok { - return - } - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - AttachmentAccessModifier: attachmentAccess, - Pos: member.Identifier.Pos, + var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + switch memberAccess := member.Access.(type) { + case EntitlementSetAccess: + requestedEntitlements = memberAccess.Entitlements + // if the attachment field/function is declared with mapped access, the domain of the map must be a + // subset of the supported entitlements on the base. This is because the attachment reference + // will never be able to possess any entitlements other than these, so any map relations that map + // from other entitlements will be unreachable + case *EntitlementMapAccess: + requestedEntitlements = memberAccess.Domain().Entitlements + } + + requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { + if !supportedBaseEntitlements.Contains(entitlement) { + checker.report(&InvalidAttachmentEntitlementError{ + Attachment: attachmentType, + BaseType: baseType, + InvalidEntitlement: entitlement, + Pos: member.Identifier.Pos, + }) + } }) - }) } @@ -620,14 +591,7 @@ func (checker *Checker) declareNestedDeclarations( func (checker *Checker) declareAttachmentType(declaration *ast.AttachmentDeclaration) *CompositeType { composite := checker.declareCompositeType(declaration) - composite.baseType = checker.convertNominalType(declaration.BaseType) - - attachmentAccess := checker.accessFromAstAccess(declaration.Access) - if attachmentAccess, ok := attachmentAccess.(*EntitlementMapAccess); ok { - composite.AttachmentEntitlementAccess = attachmentAccess - } - return composite } @@ -2176,12 +2140,9 @@ func (checker *Checker) declareSelfValue(selfType Type, selfDocString string) { // inside of an attachment, self is a reference to the attachment's type, because // attachments are never first class values, they must always exist inside references if typedSelfType, ok := selfType.(*CompositeType); ok && typedSelfType.Kind == common.CompositeKindAttachment { - // the `self` value in an attachment is considered fully-entitled to that attachment, or - // equivalently the entire codomain of the attachment's map + // the `self` value in an attachment is entitled to the same entitlements required by the containing function var selfAccess Access = UnauthorizedAccess - if typedSelfType.AttachmentEntitlementAccess != nil { - selfAccess = typedSelfType.AttachmentEntitlementAccess.Codomain() - } + // EntitlementsTODO: self access should be the based on the function selfType = NewReferenceType(checker.memoryGauge, selfAccess, typedSelfType) } checker.declareLowerScopedValue(selfType, selfDocString, SelfIdentifier, common.DeclarationKindSelf) @@ -2193,17 +2154,9 @@ func (checker *Checker) declareBaseValue(baseType Type, attachmentType *Composit // to be referenced by `base` baseType = NewIntersectionType(checker.memoryGauge, []*InterfaceType{typedBaseType}) } - // the `base` value in an attachment function has the set of entitlements defined by the required entitlements specified in the attachment's declaration - // ------------------------------- - // entitlement E - // entitlement F - // access(all) attachment A for R { - // require entitlement E - // access(all) fun foo() { ... } - // } - // ------------------------------- - // within the body of `foo`, the `base` value will be entitled to `E` but not `F`, because only `E` was required in the attachment's declaration + // the `base` value in an attachment is entitled to the same entitlements required by the containing function var baseAccess Access = UnauthorizedAccess + // EntitlementsTODO: base access should be the based on the function base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 363ee94f78..f1216e5814 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1900,12 +1900,7 @@ func (checker *Checker) checkEntitlementMapAccess( containerKind *common.CompositeKind, startPos ast.Position, ) { - // attachments may be declared with an entitlement map access - if declarationKind == common.DeclarationKindAttachment { - return - } - - // otherwise, mapped entitlements may only be used in structs, resources and attachments + // mapped entitlements may only be used in structs, resources and attachments if containerKind == nil || (*containerKind != common.CompositeKindResource && *containerKind != common.CompositeKindStructure && @@ -1918,7 +1913,7 @@ func (checker *Checker) checkEntitlementMapAccess( return } - // mapped entitlement fields must be, one of: + // mapped entitlement fields must be one of: // 1) An [optional] reference that is authorized to the same mapped entitlement. // 2) A function that return an [optional] reference authorized to the same mapped entitlement. // 3) A container - So if the parent is a reference, entitlements can be granted to the resulting field reference. diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index e5ebe8f4b6..b40f3d8111 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4595,10 +4595,10 @@ func (e *AttachmentsNotEnabledError) Error() string { // InvalidAttachmentEntitlementError type InvalidAttachmentEntitlementError struct { - Attachment *CompositeType - AttachmentAccessModifier Access - InvalidEntitlement *EntitlementType - Pos ast.Position + Attachment *CompositeType + BaseType Type + InvalidEntitlement *EntitlementType + Pos ast.Position } var _ SemanticError = &InvalidAttachmentEntitlementError{} @@ -4620,15 +4620,10 @@ func (e *InvalidAttachmentEntitlementError) Error() string { } func (e *InvalidAttachmentEntitlementError) SecondaryError() string { - switch access := e.AttachmentAccessModifier.(type) { - case PrimitiveAccess: - return "attachments declared with `access(all)` access do not support entitlements on their members" - case *EntitlementMapAccess: - return fmt.Sprintf("`%s` must appear in the output of the entitlement mapping `%s`", - e.InvalidEntitlement.QualifiedIdentifier(), - access.Type.QualifiedIdentifier()) - } - return "" + return fmt.Sprintf("`%s` must appear in the base type `%s`", + e.InvalidEntitlement.QualifiedIdentifier(), + e.BaseType.String(), + ) } func (e *InvalidAttachmentEntitlementError) StartPosition() ast.Position { diff --git a/runtime/sema/type.go b/runtime/sema/type.go index cbaf02f95f..666c9867a3 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4291,9 +4291,8 @@ type CompositeType struct { // in a language with support for algebraic data types, // we would implement this as an argument to the CompositeKind type constructor. // Alas, this is Go, so for now these fields are only non-nil when Kind is CompositeKindAttachment - baseType Type - baseTypeDocString string - AttachmentEntitlementAccess *EntitlementMapAccess + baseType Type + baseTypeDocString string cachedIdentifiers *struct { TypeID TypeID @@ -4664,10 +4663,9 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. var access Access = UnauthorizedAccess switch attachment := indexingType.(type) { case *CompositeType: - attachmentEntitlementAccess := attachment.AttachmentEntitlementAccess - if attachmentEntitlementAccess != nil { - access = attachmentEntitlementAccess.Codomain() - } + // when accessed on an owned value, the produced attachment reference is entitled to all the + // entitlements it supports + access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ @@ -6131,15 +6129,11 @@ func (t *ReferenceType) TypeIndexingElementType(indexingType Type, astRange func } var access Access = UnauthorizedAccess - switch attachment := indexingType.(type) { + switch indexingType.(type) { case *CompositeType: - if attachment.AttachmentEntitlementAccess != nil { - var err error - access, err = attachment.AttachmentEntitlementAccess.Image(t.Authorization, astRange) - if err != nil { - return nil, err - } - } + // attachment access on a composite reference yields a reference to the attachment entitled to the same + // entitlements as that reference + access = t.Authorization } return &OptionalType{ @@ -7204,9 +7198,9 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a var access Access = UnauthorizedAccess switch attachment := indexingType.(type) { case *CompositeType: - if attachment.AttachmentEntitlementAccess != nil { - access = attachment.AttachmentEntitlementAccess.Codomain() - } + // when accessed on an owned value, the produced attachment reference is entitled to all the + // entitlements it supports + access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index c7afcbd537..95c53048e3 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -3583,7 +3583,7 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { t.Parallel() - t.Run("mapping allowed", func(t *testing.T) { + t.Run("mapping not allowed", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -3592,7 +3592,9 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { access(mapping E) attachment A for AnyStruct {} `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.InvalidMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement set not allowed", func(t *testing.T) { @@ -3611,7 +3613,7 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { require.IsType(t, &sema.InvalidEntitlementAccessError{}, errs[0]) }) - t.Run("mapping allowed in contract", func(t *testing.T) { + t.Run("mapping not allowed in contract", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -3624,12 +3626,14 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { X -> Y } access(mapping E) attachment A for AnyStruct { - access(Y) fun foo() {} + access(all) fun foo() {} } } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.InvalidMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement set not allowed in contract", func(t *testing.T) { @@ -4969,7 +4973,7 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { X -> Z } struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y, Z) fun foo() {} } let s = attach A() to S() From 95ab3a9714640076ee879e286eca483b5cf2f941 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 15:47:39 -0400 Subject: [PATCH 04/52] fix checker tests --- runtime/sema/check_composite_declaration.go | 83 ++-- runtime/sema/check_interface_declaration.go | 4 +- runtime/sema/check_transaction_declaration.go | 2 +- runtime/sema/type.go | 13 + runtime/tests/checker/attachments_test.go | 23 +- runtime/tests/checker/entitlements_test.go | 445 ++++++++++-------- 6 files changed, 323 insertions(+), 247 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 1ba8bedc5b..f4325ee7c2 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -66,41 +66,56 @@ func (checker *Checker) checkAttachmentBaseType(attachmentType *CompositeType, a }) } +func (checker *Checker) checkAttachmentMemberAccess( + attachmentType *CompositeType, + member *Member, + baseType Type, + supportedBaseEntitlements *EntitlementOrderedSet, +) { + var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + switch memberAccess := member.Access.(type) { + case EntitlementSetAccess: + requestedEntitlements = memberAccess.Entitlements + // if the attachment field/function is declared with mapped access, the domain of the map must be a + // subset of the supported entitlements on the base. This is because the attachment reference + // will never be able to possess any entitlements other than these, so any map relations that map + // from other entitlements will be unreachable + case *EntitlementMapAccess: + requestedEntitlements = memberAccess.Domain().Entitlements + } + + requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { + if !supportedBaseEntitlements.Contains(entitlement) { + checker.report(&InvalidAttachmentEntitlementError{ + Attachment: attachmentType, + BaseType: baseType, + InvalidEntitlement: entitlement, + Pos: member.Identifier.Pos, + }) + } + }) +} + func (checker *Checker) checkAttachmentMembersAccess(attachmentType *CompositeType) { // all the access modifiers for attachment members must be valid entitlements for the base type var supportedBaseEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} baseType := attachmentType.GetBaseType() switch base := attachmentType.GetBaseType().(type) { - case *CompositeType: + case EntitlementSupportingType: supportedBaseEntitlements = base.SupportedEntitlements() } - attachmentType.Members.Foreach(func(_ string, member *Member) { - var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} - switch memberAccess := member.Access.(type) { - case EntitlementSetAccess: - requestedEntitlements = memberAccess.Entitlements - // if the attachment field/function is declared with mapped access, the domain of the map must be a - // subset of the supported entitlements on the base. This is because the attachment reference - // will never be able to possess any entitlements other than these, so any map relations that map - // from other entitlements will be unreachable - case *EntitlementMapAccess: - requestedEntitlements = memberAccess.Domain().Entitlements - } - - requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { - if !supportedBaseEntitlements.Contains(entitlement) { - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - BaseType: baseType, - InvalidEntitlement: entitlement, - Pos: member.Identifier.Pos, - }) - } + attachmentType.EffectiveInterfaceConformanceSet().ForEach(func(intf *InterfaceType) { + intf.Members.Foreach(func(_ string, member *Member) { + checker.checkAttachmentMemberAccess(attachmentType, member, baseType, supportedBaseEntitlements) }) }) + attachmentType.Members.Foreach(func(_ string, member *Member) { + checker.checkAttachmentMemberAccess(attachmentType, member, baseType, supportedBaseEntitlements) + }) + } func (checker *Checker) VisitAttachmentDeclaration(declaration *ast.AttachmentDeclaration) (_ struct{}) { @@ -117,11 +132,11 @@ func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDe checker.visitCompositeLikeDeclaration(declaration) attachmentType := checker.Elaboration.CompositeDeclarationType(declaration) - checker.checkAttachmentMembersAccess(attachmentType) checker.checkAttachmentBaseType( attachmentType, declaration.BaseType, ) + checker.checkAttachmentMembersAccess(attachmentType) return } @@ -2013,7 +2028,7 @@ func (checker *Checker) checkSpecialFunction( fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(specialFunction.FunctionDeclaration.Access), containerKind) - checker.declareSelfValue(containerType, containerDocString) + checker.declareSelfValue(fnAccess, containerType, containerDocString) if containerType.GetCompositeKind() == common.CompositeKindAttachment { // attachments cannot be interfaces, so this cast must succeed attachmentType, ok := containerType.(*CompositeType) @@ -2021,6 +2036,7 @@ func (checker *Checker) checkSpecialFunction( panic(errors.NewUnreachableError()) } checker.declareBaseValue( + fnAccess, attachmentType.baseType, attachmentType, attachmentType.baseTypeDocString) @@ -2077,9 +2093,12 @@ func (checker *Checker) checkCompositeFunctions( checker.enterValueScope() defer checker.leaveValueScope(function.EndPosition, true) - checker.declareSelfValue(selfType, selfDocString) + fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindComposite) + + checker.declareSelfValue(fnAccess, selfType, selfDocString) if selfType.GetCompositeKind() == common.CompositeKindAttachment { checker.declareBaseValue( + fnAccess, selfType.baseType, selfType, selfType.baseTypeDocString, @@ -2136,28 +2155,24 @@ func (checker *Checker) declareLowerScopedValue( } } -func (checker *Checker) declareSelfValue(selfType Type, selfDocString string) { +func (checker *Checker) declareSelfValue(fnAccess Access, selfType Type, selfDocString string) { // inside of an attachment, self is a reference to the attachment's type, because // attachments are never first class values, they must always exist inside references if typedSelfType, ok := selfType.(*CompositeType); ok && typedSelfType.Kind == common.CompositeKindAttachment { // the `self` value in an attachment is entitled to the same entitlements required by the containing function - var selfAccess Access = UnauthorizedAccess - // EntitlementsTODO: self access should be the based on the function - selfType = NewReferenceType(checker.memoryGauge, selfAccess, typedSelfType) + selfType = NewReferenceType(checker.memoryGauge, fnAccess, typedSelfType) } checker.declareLowerScopedValue(selfType, selfDocString, SelfIdentifier, common.DeclarationKindSelf) } -func (checker *Checker) declareBaseValue(baseType Type, attachmentType *CompositeType, superDocString string) { +func (checker *Checker) declareBaseValue(fnAccess Access, baseType Type, attachmentType *CompositeType, superDocString string) { if typedBaseType, ok := baseType.(*InterfaceType); ok { // we can't actually have a value of an interface type I, so instead we create a value of {I} // to be referenced by `base` baseType = NewIntersectionType(checker.memoryGauge, []*InterfaceType{typedBaseType}) } // the `base` value in an attachment is entitled to the same entitlements required by the containing function - var baseAccess Access = UnauthorizedAccess - // EntitlementsTODO: base access should be the based on the function - base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) + base := NewReferenceType(checker.memoryGauge, fnAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 4f11f22fc1..f0eac0e6a2 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -218,7 +218,9 @@ func (checker *Checker) checkInterfaceFunctions( checker.enterValueScope() defer checker.leaveValueScope(function.EndPosition, false) - checker.declareSelfValue(selfType, selfDocString) + fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindInterface) + + checker.declareSelfValue(fnAccess, selfType, selfDocString) mustExit := false checkResourceLoss := false diff --git a/runtime/sema/check_transaction_declaration.go b/runtime/sema/check_transaction_declaration.go index f6be79dabe..345300cdcd 100644 --- a/runtime/sema/check_transaction_declaration.go +++ b/runtime/sema/check_transaction_declaration.go @@ -57,7 +57,7 @@ func (checker *Checker) VisitTransactionDeclaration(declaration *ast.Transaction checker.enterValueScope() defer checker.leaveValueScope(declaration.EndPosition, true) - checker.declareSelfValue(transactionType, "") + checker.declareSelfValue(UnauthorizedAccess, transactionType, "") if declaration.ParameterList != nil { checker.checkTransactionParameters(declaration, transactionType.Parameters) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 666c9867a3..dee7eb0cb0 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4505,6 +4505,13 @@ func (t *CompositeType) SupportedEntitlements() (set *EntitlementOrderedSet) { set.SetAll(it.SupportedEntitlements()) }) + // attachments support at least the entitlements supported by their base + if entitlementSupportingBase, isEntitlementSupportingBase := + // must ensure there is no recursive case + t.GetBaseType().(EntitlementSupportingType); isEntitlementSupportingBase && entitlementSupportingBase != t { + set.SetAll(entitlementSupportingBase.SupportedEntitlements()) + } + t.supportedEntitlements = set return set } @@ -6008,6 +6015,9 @@ func (t *ReferenceType) String() string { if t.Authorization != UnauthorizedAccess { authorization = t.Authorization.String() } + if _, isMapping := t.Authorization.(*EntitlementMapAccess); isMapping { + authorization = "mapping " + authorization + } return formatReferenceType(" ", authorization, t.Type.String()) } @@ -6019,6 +6029,9 @@ func (t *ReferenceType) QualifiedString() string { if t.Authorization != UnauthorizedAccess { authorization = t.Authorization.QualifiedString() } + if _, isMapping := t.Authorization.(*EntitlementMapAccess); isMapping { + authorization = "mapping " + authorization + } return formatReferenceType(" ", authorization, t.Type.QualifiedString()) } diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 464d5f72a7..dd3ce38bad 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -3865,13 +3865,11 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { _, err := ParseAndCheck(t, ` - access(all) resource R {} - - entitlement mapping M { - Mutate -> Insert + access(all) resource R { + access(Mutate) fun foo() {} } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(mapping Identity) let x: [String] init() { self.x = ["x"] @@ -3897,6 +3895,7 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { ` access(all) resource R { access(all) fun foo() { + // this only works because A supports all the entitlements of R self[A]!.x.append("y") } } @@ -3945,17 +3944,13 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { _, err := ParseAndCheck(t, ` - entitlement mapping M { - Mutate -> Insert - } - access(all) resource R { - access(all) fun foo() { + access(Insert) fun foo() { var xRef = self[A]!.x xRef.append("y") } } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(mapping Identity) let x: [String] init() { self.x = ["x"] @@ -4523,8 +4518,10 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { a.foo() } } - resource R {} - access(mapping M) attachment A for R { + resource R { + access(F) fun foo() {} + } + access(all) attachment A for R { access(F) fun foo() {} } access(all) fun foo(s: @R) { diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 95c53048e3..449b6ccbf7 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -969,7 +969,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { typeMismatchError.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", typeMismatchError.ActualType.QualifiedString(), ) }) @@ -1310,7 +1310,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(NM) &Int", + "auth(mapping NM) &Int", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, @@ -1397,7 +1397,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(NM) &Int", + "auth(mapping NM) &Int", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, @@ -2995,98 +2995,114 @@ func TestCheckEntitlementInheritance(t *testing.T) { assert.NoError(t, err) }) - t.Run("attachment default function entitlements", func(t *testing.T) { + t.Run("attachment inherited default entitled function entitlements on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G - entitlement mapping M { - E -> F - } - entitlement mapping N { - G -> E - } struct interface I { - access(mapping M) fun foo(): auth(mapping M) &Int { - return &1 as auth(mapping M) &Int + access(E) fun foo(): auth(F) &Int { + return &1 as auth(F) &Int } } - struct S {} - access(mapping N) attachment A for S: I {} + struct interface I2: I {} + struct S { + access(E) fun foo() {} + } + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() + let ref = &s as auth(E) &S + let attachmentRef: auth(E) &A = s[A]! + let i: auth(F) &Int = attachmentRef.foo() } `) assert.NoError(t, err) }) - t.Run("attachment inherited default function entitlements", func(t *testing.T) { + t.Run("attachment inherited default mapped function entitlements on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G entitlement mapping M { E -> F } - entitlement mapping N { - G -> E - } struct interface I { access(mapping M) fun foo(): auth(mapping M) &Int { return &1 as auth(mapping M) &Int } } struct interface I2: I {} - struct S {} - access(mapping N) attachment A for S: I2 {} + struct S { + access(E) fun foo() {} + } + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() + let ref = &s as auth(E) &S + let attachmentRef: auth(E) &A = s[A]! + let i: auth(F) &Int = attachmentRef.foo() } `) assert.NoError(t, err) }) - t.Run("attachment default function entitlements no attachment mapping", func(t *testing.T) { + t.Run("attachment inherited default mapped function entitlements not on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G entitlement mapping M { E -> F } - entitlement mapping N { - G -> E - } struct interface I { access(mapping M) fun foo(): auth(mapping M) &Int { return &1 as auth(mapping M) &Int } } + struct interface I2: I {} struct S {} - attachment A for S: I {} + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() // mismatch + let ref = &s as auth(E) &S + let i: auth(F) &Int = s[A]!.foo() } `) errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) + }) - // because A is declared with no mapping, all its references are unentitled - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + t.Run("attachment inherited default entitled function entitlements not on base", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement E + entitlement F + struct interface I { + access(E) fun foo(): auth(F) &Int { + return &1 as auth(F) &Int + } + } + struct interface I2: I {} + struct S {} + access(all) attachment A for S: I2 {} + fun test() { + let s = attach A() to S() + let ref = &s as auth(E) &S + let i: auth(F) &Int = s[A]!.foo() + } + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) }) } @@ -4608,11 +4624,10 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(Y) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y) fun entitled() { let a: auth(Y) &A = self let b: &S = base @@ -4633,7 +4648,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y) &A", + "&A", typeMismatchErr.ActualType.QualifiedString(), ) @@ -4654,12 +4669,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y - } struct S {} - access(mapping M) attachment A for S { - require entitlement X + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } @@ -4678,43 +4689,30 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(X) &S", + "&S", typeMismatchErr.ActualType.QualifiedString(), ) }) - t.Run("base type with no requirements", func(t *testing.T) { + t.Run("base type with sufficent requirements", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X - entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(X) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } - access(all) fun entitled() { + access(X) fun entitled() { let b: auth(X) &S = base } } `) - errs := RequireCheckerErrors(t, err, 1) - - var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) - assert.Equal(t, - typeMismatchErr.ExpectedType.QualifiedString(), - "auth(X) &S", - ) - assert.Equal(t, - "&S", - typeMismatchErr.ActualType.QualifiedString(), - ) + assert.NoError(t, err) }) t.Run("base type", func(t *testing.T) { @@ -4723,17 +4721,15 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(X) fun foo() {} + access(Y) fun bar() {} } - struct S {} - access(mapping M) attachment A for S { - require entitlement X - require entitlement Y + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } - access(all) fun entitled() { + access(X, Y) fun entitled() { let b: auth(X, Y) &S = base } } @@ -4742,46 +4738,78 @@ func TestCheckAttachmentEntitlements(t *testing.T) { assert.NoError(t, err) }) - t.Run("multiple mappings", func(t *testing.T) { + t.Run("base and self in mapped functions", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement E - entitlement F entitlement mapping M { X -> Y - E -> F } struct S { - access(E, X) fun foo() {} + access(X) fun foo() {} } - access(mapping M) attachment A for S { - access(F, Y) fun entitled() { - let a: auth(F, Y) &A = self + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let b: auth(mapping M) &S = base + let a: auth(mapping M) &A = self + + return &1 } - access(all) fun unentitled() { - let a: auth(F, Y, E) &A = self // err + } + `) + + assert.NoError(t, err) + }) + + t.Run("invalid base and self in mapped functions", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let b: auth(Y) &S = base + let a: auth(Y) &A = self + + return &1 } } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(F, Y, E) &A", + "auth(Y) &S", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y, F) &A", + "auth(mapping M) &S", + typeMismatchErr.ActualType.QualifiedString(), + ) + + require.ErrorAs(t, errs[1], &typeMismatchErr) + assert.Equal(t, + "auth(Y) &A", + typeMismatchErr.ExpectedType.QualifiedString(), + ) + assert.Equal(t, + "auth(mapping M) &A", typeMismatchErr.ActualType.QualifiedString(), ) }) - t.Run("missing in codomain", func(t *testing.T) { + t.Run("missing in S", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -4789,12 +4817,14 @@ func TestCheckAttachmentEntitlements(t *testing.T) { entitlement Y entitlement Z entitlement E - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(X) fun foo() {} + access(Y | Z) let bar: Int + init() { + self.bar = 1 + } } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(E) fun entitled() {} } `) @@ -4809,20 +4839,17 @@ func TestCheckAttachmentEntitlements(t *testing.T) { ) }) - t.Run("missing in codomain in set", func(t *testing.T) { + t.Run("missing in set", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X entitlement Y entitlement Z entitlement E - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y | E | Z) fun entitled() {} } `) @@ -4844,11 +4871,10 @@ func TestCheckAttachmentEntitlements(t *testing.T) { entitlement X entitlement E entitlement F - entitlement mapping M { - E -> F + struct S { + access(F) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(F, X, E) fun entitled() {} } `) @@ -4879,9 +4905,9 @@ func TestCheckAttachmentEntitlements(t *testing.T) { X -> Y } struct S { - access(Y) fun foo() {} + access(X) fun foo() {} } - access(mapping M) attachment A for S { + access(all) attachment A for S { access(mapping M) let x: auth(mapping M) &S init() { self.x = &S() as auth(Y) &S @@ -4968,11 +4994,9 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} access(all) attachment A for S { access(Y, Z) fun foo() {} } @@ -4983,6 +5007,27 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { assert.NoError(t, err) }) + t.Run("basic owned fully entitled missing X", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement Z + struct S { + access(Y, Z) fun foo() {} + } + access(all) attachment A for S { + access(Y, Z) fun foo() {} + } + let s = attach A() to S() + let a: auth(X, Y, Z) &A = s[A]! + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + t.Run("basic owned intersection fully entitled", func(t *testing.T) { t.Parallel() @@ -4990,13 +5035,13 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct interface I { + access(Y, Z) fun foo() } - struct interface I {} - struct S: I {} - access(mapping M) attachment A for I { + struct S: I { + access(Y, Z) fun foo() {} + } + access(all) attachment A for I { access(Y, Z) fun foo() {} } let s: {I} = attach A() to S() @@ -5006,33 +5051,51 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { assert.NoError(t, err) }) - t.Run("basic reference mapping", func(t *testing.T) { + t.Run("basic owned intersection fully entitled missing X", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement E - entitlement F - entitlement mapping M { - X -> Y - E -> F + entitlement Z + struct interface I { + access(Y, Z) fun foo() + } + struct S: I { + access(Y, Z) fun foo() {} + } + access(all) attachment A for I { + access(Y, Z) fun foo() {} } + let s: {I} = attach A() to S() + let a: auth(X, Y, Z) &A = s[A]! + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + + t.Run("basic reference mapping", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement E struct S { access(X, E) fun foo() {} } - access(mapping M) attachment A for S { - access(Y, F) fun foo() {} + access(all) attachment A for S { + access(X, E) fun foo() {} } let s = attach A() to S() - let yRef = &s as auth(X) &S - let fRef = &s as auth(E) &S + let xRef = &s as auth(X) &S + let eRef = &s as auth(E) &S let bothRef = &s as auth(X, E) &S - let a1: auth(Y) &A = yRef[A]! - let a2: auth(F) &A = fRef[A]! - let a3: auth(F) &A = yRef[A]! // err - let a4: auth(Y) &A = fRef[A]! // err - let a5: auth(Y, F) &A = bothRef[A]! + let a1: auth(X) &A = xRef[A]! + let a2: auth(E) &A = eRef[A]! + let a3: auth(E) &A = xRef[A]! // err + let a4: auth(X) &A = eRef[A]! // err + let a5: auth(X, E) &A = bothRef[A]! `) errs := RequireCheckerErrors(t, err, 2) @@ -5040,21 +5103,21 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(F) &A?", + "auth(E) &A?", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y) &A?", + "auth(X) &A?", typeMismatchErr.ActualType.QualifiedString(), ) require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, - "auth(Y) &A?", + "auth(X) &A?", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(F) &A?", + "auth(E) &A?", typeMismatchErr.ActualType.QualifiedString(), ) }) @@ -5063,51 +5126,15 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X - entitlement Y - entitlement mapping M { - X -> Y - } - struct S {} - access(mapping M) attachment A for S { - access(Y) fun foo() {} - } - let s = attach A() to S() - let ref = &s as &S - let a1: auth(Y) &A = ref[A]! - `) - - errs := RequireCheckerErrors(t, err, 1) - - var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) - assert.Equal(t, - "auth(Y) &A?", - typeMismatchErr.ExpectedType.QualifiedString(), - ) - assert.Equal(t, - "&A?", - typeMismatchErr.ActualType.QualifiedString(), - ) - }) - - t.Run("entitled access access(all) attachment", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement X entitlement Y - entitlement mapping M { - X -> Y - } struct S { - access(X) fun foo() {} + access(Y) fun foo() {} } access(all) attachment A for S { - access(all) fun foo() {} + access(Y) fun foo() {} } let s = attach A() to S() - let ref = &s as auth(X) &S + let ref = &s as &S let a1: auth(Y) &A = ref[A]! `) @@ -5160,41 +5187,63 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { ) }) - t.Run("unrepresentable access mapping", func(t *testing.T) { + t.Run("base attachment access in mapped function", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X - entitlement Y - entitlement Z - entitlement E - entitlement F - entitlement G + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let s: auth(mapping M) &A = base[A]! - entitlement mapping M { - X -> Y - X -> Z - E -> F - E -> G - } + return &1 + } + } + `) - struct S { - access(X, E) fun foo() {} - } + assert.NoError(t, err) + }) - access(mapping M) attachment A for S { - access(Y, Z, F, G) fun foo() {} - } + t.Run("invalid base attachment access in mapped function", func(t *testing.T) { + t.Parallel() - let s = attach A() to S() - let ref = (&s as auth(X) &S) as auth(X | E) &S - let a1 = ref[A]! + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let s: auth(Y) &A? = base[A] + + return &1 + } + } `) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.UnrepresentableEntitlementMapOutputError{}, errs[0]) - require.IsType(t, &sema.InvalidTypeIndexingError{}, errs[1]) + var typeMismatchErr *sema.TypeMismatchError + require.ErrorAs(t, errs[0], &typeMismatchErr) + assert.Equal(t, + "auth(Y) &A?", + typeMismatchErr.ExpectedType.QualifiedString(), + ) + assert.Equal(t, + "auth(mapping M) &A?", + typeMismatchErr.ActualType.QualifiedString(), + ) }) } From ed594f38faa4258bdf5eaf94454ed6c6317d0a52 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 10:29:54 -0400 Subject: [PATCH 05/52] begin interpreter impl --- runtime/interpreter/value.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c989a4224b..108ebb2681 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17820,30 +17820,23 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc ) } -func attachmentReferenceAuthorization( - interpreter *Interpreter, - attachmentType *sema.CompositeType, - baseAccess sema.Access, -) (Authorization, error) { - // The attachment reference has the same entitlements as the base access - return ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), nil -} - func attachmentBaseAuthorization( interpreter *Interpreter, attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess + // EntitlementsTODO: this should not be unauthorized return auth } func attachmentBaseAndSelfValues( interpreter *Interpreter, + fnAccess sema.Access, v *CompositeValue, ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { base = v.getBaseValue() - var attachmentReferenceAuth Authorization = UnauthorizedAccess + attachmentReferenceAuth := ConvertSemaAccessToStaticAuthorization(interpreter, fnAccess) // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) @@ -17898,15 +17891,16 @@ func (v *CompositeValue) getTypeKey( return Nil } attachmentType := keyType.(*sema.CompositeType) - // dynamically set the attachment's base to this composite, but with authorization based on the requested access on that attachment + // dynamically set the attachment's base to this composite attachment.setBaseValue(interpreter, v) - // Map the entitlements of the accessing reference through the attachment's entitlement map to get the authorization of this reference - attachmentReferenceAuth, err := attachmentReferenceAuthorization(interpreter, attachmentType, baseAccess) - if err != nil { - return Nil - } - attachmentRef := NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, attachment, attachmentType) + // The attachment reference has the same entitlements as the base access + attachmentRef := NewEphemeralReferenceValue( + interpreter, + ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), + attachment, + attachmentType, + ) interpreter.trackReferencedResourceKindedValue(attachment.StorageID(), attachment) return NewSomeValueNonCopying(interpreter, attachmentRef) From 9d6287ef7033a072e50a68ee6ecd0f74f16c1462 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:25:12 -0400 Subject: [PATCH 06/52] base has different auth in each function --- runtime/interpreter/interpreter.go | 3 +-- runtime/interpreter/interpreter_expression.go | 9 ++++++- runtime/interpreter/value.go | 27 ++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index e4d4baf11c..7347bee4fd 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1295,9 +1295,8 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( var auth Authorization = UnauthorizedAccess attachmentType := interpreter.MustSemaTypeOfValue(value).(*sema.CompositeType) - // Self's type in the constructor is codomain of the attachment's entitlement map, since + // Self's type in the constructor is fully entitled, since // the constructor can only be called when in possession of the base resource - // if the attachment is declared with access(all) access, then self is unauthorized auth = ConvertSemaAccessToStaticAuthorization( interpreter, diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 179f1bb800..6035c7b31c 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1408,7 +1408,12 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta // set it on the attachment's `CompositeValue` yet, because the value does not exist. // Instead, we create an implicit constructor argument containing a reference to the base. - var auth Authorization = UnauthorizedAccess + // within the constructor, the attachment's base and self references should be fully entitled, + // as the constructor of the attachment is only callable by the owner of the base + baseType := interpreter.MustSemaTypeOfValue(base).(sema.EntitlementSupportingType) + baseAccess := sema.NewEntitlementSetAccessFromSet(baseType.SupportedEntitlements(), sema.Conjunction) + auth := ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) + attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) var baseValue Value = NewEphemeralReferenceValue( @@ -1440,6 +1445,8 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta nil, ).(*CompositeValue) + attachment.setBaseValue(interpreter, base) + // we enforce this in the checker if !ok { panic(errors.NewUnreachableError()) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 108ebb2681..28e4510a28 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16367,7 +16367,7 @@ type CompositeValue struct { // 2) When a resource `r`'s destructor is invoked, all of `r`'s attachments' destructors will also run, and // have their `base` fields set to `&r` // 3) When a value is transferred, this field is copied between its attachments - base *EphemeralReferenceValue + base *CompositeValue QualifiedIdentifier string Kind common.CompositeKind isDestroyed bool @@ -16645,7 +16645,9 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio var base *EphemeralReferenceValue var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { - base, self = attachmentBaseAndSelfValues(interpreter, v) + attachmentType := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) + destructorAccess := sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction) + base, self = attachmentBaseAndSelfValues(interpreter, destructorAccess, v) } invocation := NewInvocation( interpreter, @@ -17205,7 +17207,7 @@ func (v *CompositeValue) ConformsToStaticType( } if compositeType.Kind == common.CompositeKindAttachment { - base := v.getBaseValue().Value + base := v.getBaseValue(interpreter, UnauthorizedAccess).Value if base == nil || !base.ConformsToStaticType(interpreter, locationRange, results) { return false } @@ -17729,11 +17731,7 @@ func NewEnumCaseValue( return v } -func (v *CompositeValue) getBaseValue() *EphemeralReferenceValue { - return v.base -} - -func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { +func (v *CompositeValue) getBaseValue(interpreter *Interpreter, fnAuth Authorization) *EphemeralReferenceValue { attachmentType, ok := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) if !ok { panic(errors.NewUnreachableError()) @@ -17747,9 +17745,12 @@ func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeV baseType = ty } - authorization := attachmentBaseAuthorization(interpreter, v) - v.base = NewEphemeralReferenceValue(interpreter, authorization, base, baseType) - interpreter.trackReferencedResourceKindedValue(base.StorageID(), base) + interpreter.trackReferencedResourceKindedValue(v.base.StorageID(), v.base) + return NewEphemeralReferenceValue(interpreter, fnAuth, v.base, baseType) +} + +func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { + v.base = base } func attachmentMemberName(ty sema.Type) string { @@ -17822,6 +17823,7 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc func attachmentBaseAuthorization( interpreter *Interpreter, + fnAccess sema.Access, attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess @@ -17834,10 +17836,9 @@ func attachmentBaseAndSelfValues( fnAccess sema.Access, v *CompositeValue, ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { - base = v.getBaseValue() - attachmentReferenceAuth := ConvertSemaAccessToStaticAuthorization(interpreter, fnAccess) + base = v.getBaseValue(interpreter, attachmentReferenceAuth) // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) interpreter.trackReferencedResourceKindedValue(v.StorageID(), v) From 933b54b0c2f8a45839245a218c9f79e277f9cff3 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:50:06 -0400 Subject: [PATCH 07/52] function types carry access information --- runtime/convertValues_test.go | 1 + runtime/interpreter/interpreter.go | 2 + runtime/interpreter/value_function_test.go | 2 + runtime/interpreter/value_test.go | 1 + runtime/sema/check_composite_declaration.go | 3 ++ runtime/sema/checker.go | 2 + runtime/sema/crypto_algorithm_types.go | 2 + runtime/sema/meta_type.go | 1 + runtime/sema/runtime_type_constructors.go | 11 ++++ runtime/sema/string_type.go | 11 ++++ runtime/sema/type.go | 52 ++++++++++++++++++- runtime/sema/type_test.go | 2 + runtime/stdlib/account.go | 2 + runtime/stdlib/block.go | 2 + runtime/stdlib/log.go | 1 + runtime/stdlib/panic.go | 1 + runtime/stdlib/publickey.go | 1 + runtime/stdlib/random.go | 1 + runtime/tests/checker/interface_test.go | 1 + runtime/tests/interpreter/interface_test.go | 3 ++ runtime/tests/interpreter/interpreter_test.go | 1 + runtime/tests/interpreter/runtimetype_test.go | 3 ++ 22 files changed, 105 insertions(+), 1 deletion(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 52eda546de..5174b61c64 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -131,6 +131,7 @@ func TestRuntimeExportValue(t *testing.T) { testFunction := &interpreter.InterpretedFunctionValue{ Type: sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.VoidTypeAnnotation, ), diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 357d4d44ea..256935b04f 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -45,6 +45,7 @@ import ( var emptyImpureFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.VoidTypeAnnotation, ) @@ -3558,6 +3559,7 @@ func functionTypeFunction(invocation Invocation) Value { interpreter, sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, parameterTypes, sema.NewTypeAnnotation(returnType), ), diff --git a/runtime/interpreter/value_function_test.go b/runtime/interpreter/value_function_test.go index ffcfdfddd3..c40143298e 100644 --- a/runtime/interpreter/value_function_test.go +++ b/runtime/interpreter/value_function_test.go @@ -45,6 +45,7 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) @@ -71,6 +72,7 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index cf79dfed79..962a3d5221 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -3619,6 +3619,7 @@ func TestValue_ConformsToStaticType(t *testing.T) { functionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { TypeAnnotation: sema.IntTypeAnnotation, diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 574d6d02c9..35883323ca 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1312,11 +1312,13 @@ func (checker *Checker) checkCompositeLikeConformance( initializerType := NewSimpleFunctionType( compositeType.ConstructorPurity, + UnauthorizedAccess, compositeType.ConstructorParameters, VoidTypeAnnotation, ) interfaceInitializerType := NewSimpleFunctionType( conformance.InitializerPurity, + UnauthorizedAccess, conformance.InitializerParameters, VoidTypeAnnotation, ) @@ -2085,6 +2087,7 @@ func (checker *Checker) checkSpecialFunction( functionType := NewSimpleFunctionType( purity, + fnAccess, parameters, VoidTypeAnnotation, ) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 363ee94f78..f9e018df68 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -132,6 +132,7 @@ var _ ast.ExpressionVisitor[Type] = &Checker{} var baseFunctionType = NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, VoidTypeAnnotation, ) @@ -1128,6 +1129,7 @@ func (checker *Checker) convertFunctionType(t *ast.FunctionType) Type { return NewSimpleFunctionType( purity, + UnauthorizedAccess, parameters, returnTypeAnnotation, ) diff --git a/runtime/sema/crypto_algorithm_types.go b/runtime/sema/crypto_algorithm_types.go index b74d326bff..a084b634d5 100644 --- a/runtime/sema/crypto_algorithm_types.go +++ b/runtime/sema/crypto_algorithm_types.go @@ -110,6 +110,7 @@ const HashAlgorithmTypeHashFunctionName = "hash" var HashAlgorithmTypeHashFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -128,6 +129,7 @@ const HashAlgorithmTypeHashWithTagFunctionName = "hashWithTag" var HashAlgorithmTypeHashWithTagFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/meta_type.go b/runtime/sema/meta_type.go index 6ee455c1ab..689f170eef 100644 --- a/runtime/sema/meta_type.go +++ b/runtime/sema/meta_type.go @@ -50,6 +50,7 @@ var MetaTypeAnnotation = NewTypeAnnotation(MetaType) var MetaTypeIsSubtypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: "of", diff --git a/runtime/sema/runtime_type_constructors.go b/runtime/sema/runtime_type_constructors.go index 86bec84979..f23779af26 100644 --- a/runtime/sema/runtime_type_constructors.go +++ b/runtime/sema/runtime_type_constructors.go @@ -26,6 +26,7 @@ type RuntimeTypeConstructor struct { var MetaTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -34,6 +35,7 @@ const OptionalTypeFunctionName = "OptionalType" var OptionalTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -48,6 +50,7 @@ const VariableSizedArrayTypeFunctionName = "VariableSizedArrayType" var VariableSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -62,6 +65,7 @@ const ConstantSizedArrayTypeFunctionName = "ConstantSizedArrayType" var ConstantSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "type", @@ -83,6 +87,7 @@ const DictionaryTypeFunctionName = "DictionaryType" var DictionaryTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -100,6 +105,7 @@ const CompositeTypeFunctionName = "CompositeType" var CompositeTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -114,6 +120,7 @@ const InterfaceTypeFunctionName = "InterfaceType" var InterfaceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -128,6 +135,7 @@ const FunctionTypeFunctionName = "FunctionType" var FunctionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "parameters", @@ -149,6 +157,7 @@ const IntersectionTypeFunctionName = "IntersectionType" var IntersectionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "types", @@ -166,6 +175,7 @@ const ReferenceTypeFunctionName = "ReferenceType" var ReferenceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "entitlements", @@ -187,6 +197,7 @@ const CapabilityTypeFunctionName = "CapabilityType" var CapabilityTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/string_type.go b/runtime/sema/string_type.go index bcb562c6d6..0d7ff70407 100644 --- a/runtime/sema/string_type.go +++ b/runtime/sema/string_type.go @@ -128,6 +128,7 @@ func init() { var StringTypeConcatFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -146,6 +147,7 @@ Returns a new string which contains the given string concatenated to the end of var StringTypeSliceFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -192,6 +194,7 @@ var ByteArrayArrayTypeAnnotation = NewTypeAnnotation(ByteArrayArrayType) var StringTypeDecodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -219,6 +222,7 @@ The byte array of the UTF-8 encoding var StringTypeToLowerFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -245,6 +249,7 @@ var StringFunctionType = func() *FunctionType { functionType := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -302,6 +307,7 @@ var StringFunctionType = func() *FunctionType { var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -314,6 +320,7 @@ var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -330,6 +337,7 @@ var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -344,6 +352,7 @@ var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( var StringTypeJoinFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -362,6 +371,7 @@ var StringTypeJoinFunctionType = NewSimpleFunctionType( var StringTypeSplitFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "separator", @@ -377,6 +387,7 @@ var StringTypeSplitFunctionType = NewSimpleFunctionType( var StringTypeReplaceAllFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "of", diff --git a/runtime/sema/type.go b/runtime/sema/type.go index cf7be4bf8e..bdc8b90dc9 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -406,6 +406,7 @@ const IsInstanceFunctionName = "isInstance" var IsInstanceFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -426,6 +427,7 @@ const GetTypeFunctionName = "getType" var GetTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -440,6 +442,7 @@ const ToStringFunctionName = "toString" var ToStringFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -477,6 +480,7 @@ func FromStringFunctionDocstring(ty Type) string { func FromStringFunctionType(ty Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -506,6 +510,7 @@ func FromBigEndianBytesFunctionDocstring(ty Type) string { func FromBigEndianBytesFunctionType(ty Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -527,6 +532,7 @@ const ToBigEndianBytesFunctionName = "toBigEndianBytes" var ToBigEndianBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -809,6 +815,7 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { return &FunctionType{ Purity: functionPurity, + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -819,6 +826,7 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { TypeAnnotation: NewTypeAnnotation( &FunctionType{ Purity: functionPurity, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -1025,6 +1033,7 @@ var SaturatingArithmeticTypeFunctionTypes = map[Type]*FunctionType{} func registerSaturatingArithmeticType(t Type) { SaturatingArithmeticTypeFunctionTypes[t] = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2372,6 +2381,7 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2380,6 +2390,7 @@ func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2388,6 +2399,7 @@ func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { func ArrayRemoveFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2401,6 +2413,7 @@ func ArrayRemoveFunctionType(elementType Type) *FunctionType { func ArrayInsertFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2420,6 +2433,7 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { typeAnnotation := NewTypeAnnotation(arrayType) return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2434,6 +2448,7 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "of", @@ -2448,6 +2463,7 @@ func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { func ArrayContainsFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2462,6 +2478,7 @@ func ArrayContainsFunctionType(elementType Type) *FunctionType { func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2476,6 +2493,7 @@ func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { func ArrayAppendFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2490,6 +2508,7 @@ func ArrayAppendFunctionType(elementType Type) *FunctionType { func ArraySliceFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -2509,6 +2528,7 @@ func ArraySliceFunctionType(elementType Type) *FunctionType { func ArrayReverseFunctionType(arrayType ArrayType) *FunctionType { return &FunctionType{ Parameters: []Parameter{}, + Access: UnauthorizedAccess, ReturnTypeAnnotation: NewTypeAnnotation(arrayType), Purity: FunctionPurityView, } @@ -2518,6 +2538,7 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * // fun filter(_ function: ((T): Bool)): [T] // funcType: elementType -> Bool funcType := &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2529,6 +2550,7 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * } return &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2567,6 +2589,7 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * // transformFuncType: elementType -> U transformFuncType := &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2577,6 +2600,7 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * } return &FunctionType{ + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -3153,6 +3177,7 @@ func (p FunctionPurity) String() string { type FunctionType struct { Purity FunctionPurity + Access Access ReturnTypeAnnotation TypeAnnotation Arity *Arity ArgumentExpressionsCheck ArgumentExpressionsCheck @@ -3166,11 +3191,13 @@ type FunctionType struct { func NewSimpleFunctionType( purity FunctionPurity, + access Access, parameters []Parameter, returnTypeAnnotation TypeAnnotation, ) *FunctionType { return &FunctionType{ Purity: purity, + Access: access, Parameters: parameters, ReturnTypeAnnotation: returnTypeAnnotation, } @@ -3524,6 +3551,7 @@ func (t *FunctionType) RewriteWithIntersectionTypes() (Type, bool) { return &FunctionType{ Purity: t.Purity, + Access: t.Access, TypeParameters: rewrittenTypeParameters, Parameters: rewrittenParameters, ReturnTypeAnnotation: NewTypeAnnotation(rewrittenReturnType), @@ -3641,6 +3669,7 @@ func (t *FunctionType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type return &FunctionType{ Purity: t.Purity, + Access: t.Access, Parameters: newParameters, ReturnTypeAnnotation: NewTypeAnnotation(newReturnType), Arity: t.Arity, @@ -3696,7 +3725,7 @@ func (t *FunctionType) Map(gauge common.MemoryGauge, typeParamMap map[*TypeParam returnType := t.ReturnTypeAnnotation.Map(gauge, typeParamMap, f) - functionType := NewSimpleFunctionType(t.Purity, newParameters, returnType) + functionType := NewSimpleFunctionType(t.Purity, t.Access, newParameters, returnType) functionType.TypeParameters = newTypeParameters return f(functionType) } @@ -4020,6 +4049,7 @@ func init() { func NumberConversionFunctionType(numberType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4054,6 +4084,7 @@ func baseFunctionVariable(name string, ty *FunctionType, docString string) *Vari var AddressConversionFunctionType = &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4084,6 +4115,7 @@ Returns an Address from the given byte array var AddressTypeFromBytesFunctionType = &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4189,6 +4221,7 @@ func numberFunctionArgumentExpressionsChecker(targetType Type) ArgumentExpressio func pathConversionFunctionType(pathType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "identifier", @@ -4225,6 +4258,7 @@ func init() { typeName, &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{{Name: "T"}}, ReturnTypeAnnotation: MetaTypeAnnotation, }, @@ -4706,6 +4740,7 @@ func CompositeForEachAttachmentFunctionType(t common.CompositeKind) *FunctionTyp Identifier: "f", TypeAnnotation: NewTypeAnnotation( &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -4808,6 +4843,7 @@ func (t *CompositeType) SetNestedType(name string, nestedType ContainedType) { func (t *CompositeType) ConstructorFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, + Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: NewTypeAnnotation(t), @@ -4817,6 +4853,7 @@ func (t *CompositeType) ConstructorFunctionType() *FunctionType { func (t *CompositeType) InitializerFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, + Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -5812,6 +5849,7 @@ func (t *DictionaryType) initializeMemberResolvers() { func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -5826,6 +5864,7 @@ func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5848,6 +5887,7 @@ func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { func DictionaryRemoveFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5868,6 +5908,7 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun(K): Bool funcType := NewSimpleFunctionType( functionPurity, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5880,6 +5921,7 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun forEachKey(_ function: fun(K): Bool): Void return NewSimpleFunctionType( functionPurity, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -6318,6 +6360,7 @@ const AddressTypeToBytesFunctionName = `toBytes` var AddressTypeToBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -6806,6 +6849,7 @@ var _ Type = &TransactionType{} func (t *TransactionType) EntryPointFunctionType() *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, append(t.Parameters, t.PrepareParameters...), VoidTypeAnnotation, ) @@ -6814,6 +6858,7 @@ func (t *TransactionType) EntryPointFunctionType() *FunctionType { func (t *TransactionType) PrepareFunctionType() *FunctionType { return &FunctionType{ Purity: FunctionPurityImpure, + Access: UnauthorizedAccess, IsConstructor: true, Parameters: t.PrepareParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -6822,6 +6867,7 @@ func (t *TransactionType) PrepareFunctionType() *FunctionType { var transactionTypeExecuteFunctionType = &FunctionType{ Purity: FunctionPurityImpure, + Access: UnauthorizedAccess, IsConstructor: true, ReturnTypeAnnotation: VoidTypeAnnotation, } @@ -7447,6 +7493,7 @@ func CapabilityTypeBorrowFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: NewTypeAnnotation( &OptionalType{ @@ -7468,6 +7515,7 @@ func CapabilityTypeCheckFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: BoolTypeAnnotation, } @@ -7698,6 +7746,7 @@ var PublicKeyArrayTypeAnnotation = NewTypeAnnotation(PublicKeyArrayType) var PublicKeyVerifyFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "signature", @@ -7721,6 +7770,7 @@ var PublicKeyVerifyFunctionType = NewSimpleFunctionType( var PublicKeyVerifyPoPFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index a81854a54f..c6dcea754e 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -1988,6 +1988,7 @@ func TestMapType(t *testing.T) { } original := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -2015,6 +2016,7 @@ func TestMapType(t *testing.T) { } mapped := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index a30d3784b1..f53c2c07b7 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -43,6 +43,7 @@ Creates a new account, paid by the given existing account // auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account var accountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: "payer", @@ -2039,6 +2040,7 @@ Returns the account for the given address var getAccountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/block.go b/runtime/stdlib/block.go index 9edaf3a626..457e80d956 100644 --- a/runtime/stdlib/block.go +++ b/runtime/stdlib/block.go @@ -34,6 +34,7 @@ Returns the current block, i.e. the block which contains the currently executed var getCurrentBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, nil, sema.BlockTypeAnnotation, ) @@ -44,6 +45,7 @@ Returns the block at the given height. If the given block does not exist the fun var getBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: "at", diff --git a/runtime/stdlib/log.go b/runtime/stdlib/log.go index 792f8273df..389ae0a3c2 100644 --- a/runtime/stdlib/log.go +++ b/runtime/stdlib/log.go @@ -26,6 +26,7 @@ import ( var LogFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/panic.go b/runtime/stdlib/panic.go index d9f6296780..703dbe61a2 100644 --- a/runtime/stdlib/panic.go +++ b/runtime/stdlib/panic.go @@ -45,6 +45,7 @@ Terminates the program unconditionally and reports a message which explains why var panicFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/publickey.go b/runtime/stdlib/publickey.go index 854939269f..feb7802e4d 100644 --- a/runtime/stdlib/publickey.go +++ b/runtime/stdlib/publickey.go @@ -31,6 +31,7 @@ Constructs a new public key var publicKeyConstructorFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: sema.PublicKeyTypePublicKeyFieldName, diff --git a/runtime/stdlib/random.go b/runtime/stdlib/random.go index 7340db4559..20a621457d 100644 --- a/runtime/stdlib/random.go +++ b/runtime/stdlib/random.go @@ -36,6 +36,7 @@ Follow best practices to prevent security issues when using this function var unsafeRandomFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.UInt64TypeAnnotation, ) diff --git a/runtime/tests/checker/interface_test.go b/runtime/tests/checker/interface_test.go index 0eae0fcd4e..5acae0ba8f 100644 --- a/runtime/tests/checker/interface_test.go +++ b/runtime/tests/checker/interface_test.go @@ -1772,6 +1772,7 @@ func TestCheckInvalidInterfaceUseAsTypeSuggestion(t *testing.T) { assert.Equal(t, &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.NewTypeAnnotation( diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index a66b429611..937bc7edde 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -563,6 +563,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -675,6 +676,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -787,6 +789,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index b9b34572b4..001fd7472f 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -9977,6 +9977,7 @@ func TestInterpretHostFunctionStaticType(t *testing.T) { nil, &sema.FunctionType{ Purity: sema.FunctionPurityView, + Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.MetaTypeAnnotation, }, ), diff --git a/runtime/tests/interpreter/runtimetype_test.go b/runtime/tests/interpreter/runtimetype_test.go index d3411a8880..f2320f72b8 100644 --- a/runtime/tests/interpreter/runtimetype_test.go +++ b/runtime/tests/interpreter/runtimetype_test.go @@ -410,6 +410,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.StringTypeAnnotation, @@ -426,6 +427,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ {TypeAnnotation: sema.StringTypeAnnotation}, {TypeAnnotation: sema.IntTypeAnnotation}, @@ -441,6 +443,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.StringTypeAnnotation, }, }, From df59d508cc7111756d1f760342d57a5cb593c243 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:58:05 -0400 Subject: [PATCH 08/52] use function access for base --- runtime/interpreter/value.go | 13 ++-------- .../tests/interpreter/entitlements_test.go | 24 +++++++------------ 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 5822e20459..cbd1dff04c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16774,7 +16774,8 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var base *EphemeralReferenceValue var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { - base, self = attachmentBaseAndSelfValues(interpreter, v) + functionAccess := function.FunctionType().Access + base, self = attachmentBaseAndSelfValues(interpreter, functionAccess, v) } return NewBoundFunctionValue(interpreter, function, &self, base, nil) } @@ -17747,16 +17748,6 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc ) } -func attachmentBaseAuthorization( - interpreter *Interpreter, - fnAccess sema.Access, - attachment *CompositeValue, -) Authorization { - var auth Authorization = UnauthorizedAccess - // EntitlementsTODO: this should not be unauthorized - return auth -} - func attachmentBaseAndSelfValues( interpreter *Interpreter, fnAccess sema.Access, diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index b9c4b5c40b..a394a5c44e 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -2166,15 +2166,12 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S {} + access(all) attachment A for S {} fun test(): auth(Y, Z) &A { let s = attach A() to S() return s[A]! @@ -2200,20 +2197,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(Y | Z) fun entitled(): auth(Y, Z) &A { + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &A { return self } } - fun test(): auth(Y, Z) &A { + fun test(): auth(Y | Z) &A { let s = attach A() to S() return s[A]!.entitled() } @@ -2228,7 +2222,7 @@ func TestInterpretEntitledAttachments(t *testing.T) { nil, func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, 2, - sema.Conjunction, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) From 1cafcabd304cc48615c85311badb2a3b6788215f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:58:27 -0400 Subject: [PATCH 09/52] add missing access --- runtime/sema/checker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index f9e018df68..316406bc70 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1354,6 +1354,7 @@ func (checker *Checker) functionType( return &FunctionType{ Purity: PurityFromAnnotation(purity), + Access: access, TypeParameters: convertedTypeParameters, Parameters: convertedParameters, ReturnTypeAnnotation: convertedReturnTypeAnnotation, From fb5142969ed433955a859b09ce864b28e7e37bf5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:36:51 -0400 Subject: [PATCH 10/52] update interpreter tests --- runtime/interpreter/statictype.go | 3 +- runtime/sema/type_test.go | 6 +- runtime/tests/interpreter/attachments_test.go | 21 +- .../tests/interpreter/entitlements_test.go | 334 ++++++++---------- 4 files changed, 157 insertions(+), 207 deletions(-) diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index 975ecbd29d..ee727f7802 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -25,6 +25,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" @@ -931,7 +932,7 @@ func ConvertSemaAccessToStaticAuthorization( ) Authorization { switch access := access.(type) { case sema.PrimitiveAccess: - if access.Equal(sema.UnauthorizedAccess) { + if access.Equal(sema.UnauthorizedAccess) || access.Equal(sema.PrimitiveAccess(ast.AccessNotSpecified)) { return UnauthorizedAccess } diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index c6dcea754e..d3fed2e804 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2164,7 +2164,7 @@ func TestReferenceType_String(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", referenceType.String(), ) }) @@ -2218,7 +2218,7 @@ func TestReferenceType_QualifiedString(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", referenceType.QualifiedString(), ) }) @@ -2254,7 +2254,7 @@ func TestReferenceType_QualifiedString(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(C.M) &Int", + "auth(mapping C.M) &Int", referenceType.QualifiedString(), ) }) diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index edbc1d5a95..9c3ac1370e 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1943,32 +1943,23 @@ func TestInterpretForEachAttachment(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement E entitlement F - entitlement X entitlement Y - entitlement mapping M { - E -> F - } - entitlement mapping N { - X -> Y - } - entitlement mapping O { - E -> Y + struct S { + access(F, Y) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(F) fun foo(_ x: Int): Int { return 7 + x } } - access(mapping N) attachment B for S { + access(all) attachment B for S { access(Y) fun foo(): Int { return 10 } } - access(mapping O) attachment C for S { + access(all) attachment C for S { access(Y) fun foo(_ x: Int): Int { return 8 + x } } fun test(): Int { var s = attach C() to attach B() to attach A() to S() - let ref = &s as auth(E) &S + let ref = &s as auth(F, Y) &S var i = 0 ref.forEachAttachment(fun(attachmentRef: &AnyStructAttachment) { if let a = attachmentRef as? auth(F) &A { diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index a394a5c44e..869ee96828 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -2232,20 +2232,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(Y | Z) fun entitled(): &S { + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &S { return base } } - fun test(): &S { + fun test(): auth(Y | Z) &S { let s = attach A() to S() return s[A]!.entitled() } @@ -2256,32 +2253,70 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.True( t, - interpreter.UnauthorizedAccess.Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic call return authorized base", func(t *testing.T) { + t.Run("basic call unbound method", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} + } + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &A { + return self + } } - struct S {} - access(mapping M) attachment A for S { - require entitlement X - access(Y | Z) fun entitled(): auth(X) &S { + fun test(): auth(Y | Z) &A { + let s = attach A() to S() + let foo = s[A]!.entitled + return foo() + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.True( + t, + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + ) + }) + + t.Run("basic call unbound method base", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement Y + entitlement Z + struct S { + access(Y | Z) fun foo() {} + } + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &S { return base } } - fun test(): auth(X) &S { - let s = attach A() to S() with (X) - return s[A]!.entitled() + fun test(): auth(Y | Z) &S { + let s = attach A() to S() + let foo = s[A]!.entitled + return foo() } `) @@ -2292,9 +2327,9 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.X"} }, - 1, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) @@ -2305,21 +2340,13 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S {} - fun test(): auth(F, G) &A { + access(all) attachment A for S {} + fun test(): auth(E) &A { let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]! @@ -2333,8 +2360,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2346,25 +2373,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(F | Z) fun entitled(): auth(Y, Z, F, G) &A { + access(all) attachment A for S { + access(E | G) fun entitled(): auth(E | G) &A { return self } } - fun test(): auth(Y, Z, F, G) &A { + fun test(): auth(E | G) &A { let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]!.entitled() @@ -2378,40 +2397,32 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G", "S.test.Y", "S.test.Z"} }, - 4, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.E", "S.test.G"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic ref call return base", func(t *testing.T) { + t.Run("basic ref call conjunction", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(F | Z) fun entitled(): &S { - return base + access(all) attachment A for S { + access(E) fun entitled(): auth(E) &A { + return self } } - fun test(): &S { + fun test(): auth(E) &A { let s = attach A() to S() - let ref = &s as auth(E) &S + let ref = &s as auth(E, G) &S return ref[A]!.entitled() } `) @@ -2421,37 +2432,33 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.True( t, - interpreter.UnauthorizedAccess.Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, + sema.Conjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic ref call return entitled base", func(t *testing.T) { + t.Run("basic ref call return base", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - require entitlement E - access(F | Z) fun entitled(): auth(E) &S { + access(all) attachment A for S { + access(X | E) fun entitled(): auth(X | E) &S { return base } } - fun test(): auth(E) &S { - let s = attach A() to S() with(E) + fun test(): auth(X | E) &S { + let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]!.entitled() } @@ -2464,9 +2471,9 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.E"} }, - 1, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.E", "S.test.X"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) @@ -2477,24 +2484,18 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S: I { + access(X, E) fun foo() {} + } + struct interface I { + access(X, E, G) fun foo() } - struct S: I {} - struct interface I {} - access(mapping M) attachment A for I {} - fun test(): auth(F, G) &A { + access(all) attachment A for I {} + fun test(): auth(G) &A { let s = attach A() to S() - let ref = &s as auth(E) &{I} + let ref = &s as auth(G) &{I} return ref[A]! } `) @@ -2506,8 +2507,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.G"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2521,21 +2522,13 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R {} - fun test(): auth(F, G) &A { + access(all) attachment A for R {} + fun test(): auth(E) &A { let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) let ref = account.storage.borrow(from: /storage/foo)! @@ -2552,8 +2545,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2567,28 +2560,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - access(F | Z) fun entitled(): auth(F, G, Y, Z) &A { + access(all) attachment A for R { + access(X, E) fun entitled(): auth(X, E) &A { return self } } - fun test(): auth(F, G, Y, Z) &A { + fun test(): auth(X, E) &A { let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) - let ref = account.storage.borrow(from: /storage/foo)! + let ref = account.storage.borrow(from: /storage/foo)! return ref[A]!.entitled() } `, sema.Config{ @@ -2602,8 +2587,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G", "S.test.Y", "S.test.Z"} }, - 4, + func() []common.TypeID { return []common.TypeID{"S.test.X", "S.test.E"} }, + 2, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2617,29 +2602,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - require entitlement X - access(F | Z) fun entitled(): auth(X) &R { + access(all) attachment A for R { + access(X) fun entitled(): auth(X) &R { return base } } fun test(): auth(X) &R { - let r <- attach A() to <-create R() with (X) + let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) - let ref = account.storage.borrow(from: /storage/foo)! + let ref = account.storage.borrow(from: /storage/foo)! return ref[A]!.entitled() } `, sema.Config{ @@ -2666,33 +2642,23 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - require entitlement E - require entitlement X + access(all) attachment A for R { init() { - let x = self as! auth(Y, Z, F, G) &A - let y = base as! auth(X, E) &R + let x = self as! auth(X, E, G) &A + let y = base as! auth(X, E, G) &R } destroy() { - let x = self as! auth(Y, Z, F, G) &A - let y = base as! auth(X, E) &R + let x = self as! auth(X, E, G) &A + let y = base as! auth(X, E, G) &R } } fun test() { - let r <- attach A() to <-create R() with (E, X) + let r <- attach A() to <-create R() destroy r } `) @@ -2701,28 +2667,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.NoError(t, err) }) - t.Run("composed mapped attachment access", func(t *testing.T) { + t.Run("composed attachment access", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X - entitlement Y entitlement Z - entitlement E - entitlement F - entitlement G - entitlement mapping M { - X -> Y - E -> F + entitlement Y + struct S { + access(Y) fun foo() {} } - entitlement mapping N { - Z -> X - G -> F + struct T { + access(Z) fun foo() {} } - struct S {} - struct T {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(self) let t: T init(t: T) { self.t = t @@ -2731,10 +2689,10 @@ func TestInterpretEntitledAttachments(t *testing.T) { return &self.t as auth(Z) &T } } - access(mapping N) attachment B for T {} - fun test(): auth(X) &B { + access(all) attachment B for T {} + fun test(): auth(Z) &B { let s = attach A(t: attach B() to T()) to S() - let ref = &s as auth(X) &S + let ref = &s as auth(Y) &S return ref[A]!.getT()[B]! } `) @@ -2746,7 +2704,7 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.X"} }, + func() []common.TypeID { return []common.TypeID{"S.test.Z"} }, 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), From 04984bd58f2d604b8346fefcc04610a5df46f0c7 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:38:24 -0400 Subject: [PATCH 11/52] fix test --- runtime/tests/checker/function_test.go | 1 + runtime/tests/checker/member_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/runtime/tests/checker/function_test.go b/runtime/tests/checker/function_test.go index 5e1facb8cb..bfff43f279 100644 --- a/runtime/tests/checker/function_test.go +++ b/runtime/tests/checker/function_test.go @@ -537,6 +537,7 @@ func TestCheckNativeFunctionDeclaration(t *testing.T) { assert.Equal(t, sema.NewTypeAnnotation(&sema.FunctionType{ + Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{ { Identifier: "foo", diff --git a/runtime/tests/checker/member_test.go b/runtime/tests/checker/member_test.go index ea2b592220..0114187214 100644 --- a/runtime/tests/checker/member_test.go +++ b/runtime/tests/checker/member_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -274,6 +275,7 @@ func TestCheckFunctionTypeReceiverType(t *testing.T) { assert.Equal(t, &sema.FunctionType{ Purity: sema.FunctionPurityImpure, + Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{}, ReturnTypeAnnotation: sema.VoidTypeAnnotation, }, From 0bd33a6d5bcac2a29134e2f5b05936033f071acc Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:46:00 -0400 Subject: [PATCH 12/52] fix tests --- runtime/entitlements_test.go | 17 ++++++------- runtime/sema/type.go | 14 +++++++++-- runtime/tests/checker/attachments_test.go | 30 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/runtime/entitlements_test.go b/runtime/entitlements_test.go index 048d179412..bafe338e25 100644 --- a/runtime/entitlements_test.go +++ b/runtime/entitlements_test.go @@ -217,7 +217,7 @@ func TestRuntimeAccountEntitlementSaveAndLoadFail(t *testing.T) { require.ErrorAs(t, err, &interpreter.ForceCastTypeMismatchError{}) } -func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { +func TestRuntimeAccountEntitlementAttachment(t *testing.T) { t.Parallel() storage := NewTestLedger(nil, nil) @@ -226,16 +226,13 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { deployTx := DeploymentTransaction("Test", []byte(` access(all) contract Test { - access(all) entitlement X access(all) entitlement Y - access(all) entitlement mapping M { - X -> Y - } - - access(all) resource R {} + access(all) resource R { + access(Y) fun foo() {} + } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(Y) fun foo() {} } @@ -252,7 +249,7 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { prepare(signer: auth(Storage, Capabilities) &Account) { let r <- Test.createRWithA() signer.storage.save(<-r, to: /storage/foo) - let cap = signer.capabilities.storage.issue(/storage/foo) + let cap = signer.capabilities.storage.issue(/storage/foo) signer.capabilities.publish(cap, at: /public/foo) } } @@ -263,7 +260,7 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { transaction { prepare(signer: &Account) { - let ref = signer.capabilities.borrow(/public/foo)! + let ref = signer.capabilities.borrow(/public/foo)! ref[Test.A]!.foo() } } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 4d77fc44f3..fc03f8b8c8 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4706,7 +4706,12 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) + supportedEntitlements := attachment.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = UnauthorizedAccess + } else { + access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) + } } return &OptionalType{ @@ -7259,7 +7264,12 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) + supportedEntitlements := attachment.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = UnauthorizedAccess + } else { + access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) + } } return &OptionalType{ diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index dd3ce38bad..82a7b5123f 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -4725,3 +4725,33 @@ func TestCheckAttachmentPurity(t *testing.T) { assert.IsType(t, &sema.PurityError{}, errs[0]) }) } + +func TestCheckAccessOnNonEntitlementSupportingBaseCreatesUnauthorizedReference(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + access(all) contract Test { + access(all) resource R {} + access(all) attachment A for R {} + access(all) fun makeRWithA(): @R { + return <- attach A() to <-create R() + } + } + access(all) fun main(): &Test.A? { + let r <- Test.makeRWithA() + var a = r[Test.A] + + a = returnSameRef(a) + + destroy r + return a + } + + access(all) fun returnSameRef(_ ref: &Test.A?): &Test.A? { + return ref + } + `) + + require.NoError(t, err) +} From c20839e1b0474abbb78dadfd4b517d27db7c8280 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:47:51 -0400 Subject: [PATCH 13/52] dont create empty entitlement sets in the interpreter --- runtime/interpreter/value.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index cbd1dff04c..8e0b3d42ed 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17830,7 +17830,12 @@ func (v *CompositeValue) GetTypeKey( var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) if isAttachmentType { - access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) + supportedEntitlements := attachmentTyp.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = sema.UnauthorizedAccess + } else { + access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) + } } return v.getTypeKey(interpreter, locationRange, ty, access) } From 34fbe457b145e1dc5d35e7fe06250f8a576e1aad Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:50:31 -0400 Subject: [PATCH 14/52] dont create empty entitlement sets anywhere --- runtime/interpreter/interpreter.go | 2 +- runtime/interpreter/interpreter_expression.go | 2 +- runtime/interpreter/value.go | 9 ++------- runtime/sema/access.go | 8 ++++++-- runtime/sema/type.go | 14 ++------------ 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 3defcfda9d..32673f2df7 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1299,7 +1299,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( auth = ConvertSemaAccessToStaticAuthorization( interpreter, - sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction), + sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction), ) self = NewEphemeralReferenceValue(interpreter, auth, value, attachmentType) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 5cf26bb490..76e5b4e036 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1407,7 +1407,7 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta // within the constructor, the attachment's base and self references should be fully entitled, // as the constructor of the attachment is only callable by the owner of the base baseType := interpreter.MustSemaTypeOfValue(base).(sema.EntitlementSupportingType) - baseAccess := sema.NewEntitlementSetAccessFromSet(baseType.SupportedEntitlements(), sema.Conjunction) + baseAccess := sema.NewAccessFromEntitlementSet(baseType.SupportedEntitlements(), sema.Conjunction) auth := ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 8e0b3d42ed..3f6568a3c0 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16598,7 +16598,7 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { attachmentType := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) - destructorAccess := sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction) + destructorAccess := sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction) base, self = attachmentBaseAndSelfValues(interpreter, destructorAccess, v) } invocation := NewInvocation( @@ -17830,12 +17830,7 @@ func (v *CompositeValue) GetTypeKey( var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) if isAttachmentType { - supportedEntitlements := attachmentTyp.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = sema.UnauthorizedAccess - } else { - access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) - } + access = sema.NewAccessFromEntitlementSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) } return v.getTypeKey(interpreter, locationRange, ty, access) } diff --git a/runtime/sema/access.go b/runtime/sema/access.go index 6855fa4a9b..e0f49b7f37 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -72,10 +72,14 @@ func NewEntitlementSetAccess( } } -func NewEntitlementSetAccessFromSet( +func NewAccessFromEntitlementSet( set *EntitlementOrderedSet, setKind EntitlementSetKind, -) EntitlementSetAccess { +) Access { + if set.Len() == 0 { + return UnauthorizedAccess + } + return EntitlementSetAccess{ Entitlements: set, SetKind: setKind, diff --git a/runtime/sema/type.go b/runtime/sema/type.go index fc03f8b8c8..9f98dc45c9 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4706,12 +4706,7 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - supportedEntitlements := attachment.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = UnauthorizedAccess - } else { - access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) - } + access = NewAccessFromEntitlementSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ @@ -7264,12 +7259,7 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - supportedEntitlements := attachment.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = UnauthorizedAccess - } else { - access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) - } + access = NewAccessFromEntitlementSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ From 902267570db189487828dfc3c2c725c73c1d740a Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 11:07:02 -0400 Subject: [PATCH 15/52] add tests for mapped self and base types --- runtime/interpreter/interpreter_expression.go | 7 +- runtime/sema/access.go | 10 + runtime/sema/check_composite_declaration.go | 3 +- runtime/sema/check_member_expression.go | 4 +- runtime/sema/type.go | 1 + runtime/tests/checker/attachments_test.go | 303 +++++++++++++++++- runtime/tests/interpreter/attachments_test.go | 102 ++++++ 7 files changed, 423 insertions(+), 7 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 76e5b4e036..20a18c0f2a 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1180,8 +1180,11 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx switch expression.Operation { case ast.OperationFailableCast, ast.OperationForceCast: - valueStaticType := value.StaticType(interpreter) - valueSemaType := interpreter.MustConvertStaticToSemaType(valueStaticType) + // if the value itself has a mapped entitlement type in its authorization + // (e.g. if it is a reference to `self` or `base` in an attachment function with mapped access) + // substitution must also be performed on its entitlements + valueSemaType := interpreter.substituteMappedEntitlements(interpreter.MustSemaTypeOfValue(value)) + valueStaticType := ConvertSemaToStaticType(interpreter, valueSemaType) isSubType := interpreter.IsSubTypeOfSemaType(valueStaticType, expectedType) switch expression.Operation { diff --git a/runtime/sema/access.go b/runtime/sema/access.go index e0f49b7f37..1e77e71755 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -32,6 +32,7 @@ import ( type Access interface { isAccess() + isPrimitiveAccess() bool ID() TypeID String() string QualifiedString() string @@ -87,6 +88,9 @@ func NewAccessFromEntitlementSet( } func (EntitlementSetAccess) isAccess() {} +func (EntitlementSetAccess) isPrimitiveAccess() bool { + return false +} func (e EntitlementSetAccess) ID() TypeID { entitlementTypeIDs := make([]TypeID, 0, e.Entitlements.Len()) @@ -279,6 +283,9 @@ func NewEntitlementMapAccess(mapType *EntitlementMapType) *EntitlementMapAccess } func (*EntitlementMapAccess) isAccess() {} +func (*EntitlementMapAccess) isPrimitiveAccess() bool { + return false +} func (e *EntitlementMapAccess) ID() TypeID { return e.Type.ID() @@ -448,6 +455,9 @@ type PrimitiveAccess ast.PrimitiveAccess var _ Access = PrimitiveAccess(0) func (PrimitiveAccess) isAccess() {} +func (PrimitiveAccess) isPrimitiveAccess() bool { + return true +} func (PrimitiveAccess) ID() TypeID { panic(errors.NewUnreachableError()) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index f4f51e867b..bd73227c47 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2028,7 +2028,8 @@ func (checker *Checker) checkSpecialFunction( checker.enterValueScope() defer checker.leaveValueScope(specialFunction.EndPosition, checkResourceLoss) - fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(specialFunction.FunctionDeclaration.Access), containerKind) + // initializers and destructors are considered fully entitled to their container type + fnAccess := NewAccessFromEntitlementSet(containerType.SupportedEntitlements(), Conjunction) checker.declareSelfValue(fnAccess, containerType, containerDocString) if containerType.GetCompositeKind() == common.CompositeKindAttachment { diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index e2afe56770..b5fe0b52c8 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -376,7 +376,9 @@ func (checker *Checker) visitMember(expression *ast.MemberExpression, isAssignme // in the current location of the checker, along with the authorzation with which the result can be used func (checker *Checker) isReadableMember(accessedType Type, member *Member, resultingType Type, accessRange func() ast.Range) (bool, Access) { if checker.Config.AccessCheckMode.IsReadableAccess(member.Access) || - checker.containerTypes[member.ContainerType] { + // only allow references unrestricted access to members in their own container that are not entitled + // this prevents rights escalation attacks on entitlements + (member.Access.isPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { if mappedAccess, isMappedAccess := member.Access.(*EntitlementMapAccess); isMappedAccess { return checker.mapAccess(mappedAccess, accessedType, resultingType, accessRange) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 9f98dc45c9..7be297cf75 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -304,6 +304,7 @@ func TypeActivationNestedType(typeActivation *VariableActivation, qualifiedIdent // CompositeKindedType is a type which has a composite kind type CompositeKindedType interface { Type + EntitlementSupportingType GetCompositeKind() common.CompositeKind } diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 82a7b5123f..83e4e6766a 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -1394,6 +1394,29 @@ func TestCheckAttachmentSelfTyping(t *testing.T) { require.NoError(t, err) }) + + t.Run("self access restricted", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + resource R { + access(E) fun foo() {} + } + attachment Test for R { + access(E) fun bar() {} + fun foo(t: &Test) { + t.bar() + } + }`, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) } func TestCheckAttachmentType(t *testing.T) { @@ -4089,6 +4112,283 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { require.NoError(t, err) }) + + t.Run("entitlement mapped field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement G + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(G) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) let x: [String] + init() { + self.x = ["x"] + } + } + fun foo() { + let r <- attach A() to <- create R() + let a = r[A]! + let x: auth(F) &[String] = a.x + destroy r + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function self value cast", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(F) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function self value cast invalid access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(E) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function base value cast", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(F) let x: Int + init() { + self.x = 3 + } + access(E) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function base value cast invalid access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) let x: Int + init() { + self.x = 3 + } + access(F) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function self value access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(E) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + return &self.x + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function base value access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) let x: Int + init() { + self.x = 3 + } + access(F) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + return &base.x + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) } func TestCheckAttachmentsResourceReference(t *testing.T) { @@ -4510,9 +4810,6 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { ` entitlement F entitlement E - entitlement mapping M { - E -> F - } fun bar (attachmentRef: &AnyResourceAttachment) { if let a = attachmentRef as? auth(F) &A { a.foo() diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 9c3ac1370e..090e8f6741 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1846,6 +1846,108 @@ func TestInterpretAttachmentDefensiveCheck(t *testing.T) { }) } +func TestInterpretAttachmentMappedMembers(t *testing.T) { + + t.Parallel() + + t.Run("mapped self cast", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement E + entitlement F + entitlement G + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(F) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun test(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + + t.Run("mapped base cast", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(F) let x: Int + init() { + self.x = 3 + } + access(E) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun test(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + +} + func TestInterpretForEachAttachment(t *testing.T) { t.Parallel() From f7613855f6b1be00feeb39153fb80fe1692cf1a7 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 13:10:27 -0400 Subject: [PATCH 16/52] fix lint --- runtime/interpreter/interpreter.go | 3 +-- runtime/tests/checker/entitlements_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 32673f2df7..d86425c521 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1292,12 +1292,11 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( var self MemberAccessibleValue = value if declaration.Kind() == common.CompositeKindAttachment { - var auth Authorization = UnauthorizedAccess attachmentType := interpreter.MustSemaTypeOfValue(value).(*sema.CompositeType) // Self's type in the constructor is fully entitled, since // the constructor can only be called when in possession of the base resource - auth = ConvertSemaAccessToStaticAuthorization( + auth := ConvertSemaAccessToStaticAuthorization( interpreter, sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction), ) diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 449b6ccbf7..de83709179 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4694,7 +4694,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { ) }) - t.Run("base type with sufficent requirements", func(t *testing.T) { + t.Run("base type with sufficient requirements", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` From 539eb68424ac4327a67b6e6a8909ce545958e3ed Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Nov 2023 13:40:11 -0400 Subject: [PATCH 17/52] fix self/contract/account access entitlement functions --- runtime/interpreter/interpreter_expression.go | 10 +++++ runtime/interpreter/statictype.go | 3 +- runtime/interpreter/value.go | 18 +++++++++ runtime/sema/access.go | 8 ++-- runtime/sema/check_member_expression.go | 2 +- runtime/tests/interpreter/attachments_test.go | 39 +++++++++++++++++++ 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 20a18c0f2a..8a16607921 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -335,6 +335,16 @@ func (interpreter *Interpreter) checkMemberAccess( targetStaticType := target.StaticType(interpreter) + // if both the targetType and the expectedType are references, we unwrap them and instead compare their underlying type + // this is because the "real" type of the target's entitlements may not yet be populated until after the member access + // succeeds, leading to extraneous errors here. The entitlements are enforced instead at checking time. + if referenceStaticType, isReferenceStaticType := targetStaticType.(*ReferenceStaticType); isReferenceStaticType { + targetStaticType = referenceStaticType.ReferencedType + if referenceType, isReferenceType := expectedType.(*sema.ReferenceType); isReferenceType { + expectedType = referenceType.Type + } + } + if !interpreter.IsSubTypeOfSemaType(targetStaticType, expectedType) { targetSemaType := interpreter.MustConvertStaticToSemaType(targetStaticType) diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index ee727f7802..975ecbd29d 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -25,7 +25,6 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" @@ -932,7 +931,7 @@ func ConvertSemaAccessToStaticAuthorization( ) Authorization { switch access := access.(type) { case sema.PrimitiveAccess: - if access.Equal(sema.UnauthorizedAccess) || access.Equal(sema.PrimitiveAccess(ast.AccessNotSpecified)) { + if access.Equal(sema.UnauthorizedAccess) { return UnauthorizedAccess } diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 3f6568a3c0..46a2cf0408 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16775,6 +16775,24 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { functionAccess := function.FunctionType().Access + // with respect to entitlements, any access inside an attachment that is not an entitlement access + // does not provide any entitlements to base and self + // E.g. consider: + // + // access(E) fun foo() {} + // access(self) fun bar() { + // self.foo() + // } + // access(all) fun baz() { + // self.bar() + // } + // + // clearly `bar` should be callable within `baz`, but we cannot allow `foo` + // to be callable within `bar`, or it will be possible to access `E` entitled + // methods on `base` + if functionAccess.IsPrimitiveAccess() { + functionAccess = sema.UnauthorizedAccess + } base, self = attachmentBaseAndSelfValues(interpreter, functionAccess, v) } return NewBoundFunctionValue(interpreter, function, &self, base, nil) diff --git a/runtime/sema/access.go b/runtime/sema/access.go index 1e77e71755..ae6d976ad4 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -32,7 +32,7 @@ import ( type Access interface { isAccess() - isPrimitiveAccess() bool + IsPrimitiveAccess() bool ID() TypeID String() string QualifiedString() string @@ -88,7 +88,7 @@ func NewAccessFromEntitlementSet( } func (EntitlementSetAccess) isAccess() {} -func (EntitlementSetAccess) isPrimitiveAccess() bool { +func (EntitlementSetAccess) IsPrimitiveAccess() bool { return false } @@ -283,7 +283,7 @@ func NewEntitlementMapAccess(mapType *EntitlementMapType) *EntitlementMapAccess } func (*EntitlementMapAccess) isAccess() {} -func (*EntitlementMapAccess) isPrimitiveAccess() bool { +func (*EntitlementMapAccess) IsPrimitiveAccess() bool { return false } @@ -455,7 +455,7 @@ type PrimitiveAccess ast.PrimitiveAccess var _ Access = PrimitiveAccess(0) func (PrimitiveAccess) isAccess() {} -func (PrimitiveAccess) isPrimitiveAccess() bool { +func (PrimitiveAccess) IsPrimitiveAccess() bool { return true } diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index b5fe0b52c8..a9fe2e837e 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -378,7 +378,7 @@ func (checker *Checker) isReadableMember(accessedType Type, member *Member, resu if checker.Config.AccessCheckMode.IsReadableAccess(member.Access) || // only allow references unrestricted access to members in their own container that are not entitled // this prevents rights escalation attacks on entitlements - (member.Access.isPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { + (member.Access.IsPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { if mappedAccess, isMappedAccess := member.Access.(*EntitlementMapAccess); isMappedAccess { return checker.mapAccess(mappedAccess, accessedType, resultingType, accessRange) diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 090e8f6741..d9225b51da 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1846,6 +1846,45 @@ func TestInterpretAttachmentDefensiveCheck(t *testing.T) { }) } +func TestInterpretAttachmentSelfAccessMembers(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) resource R{ + access(all) fun baz() {} + } + access(all) attachment A for R{ + access(all) fun foo() {} + access(self) fun qux1() { + self.foo() + base.baz() + } + access(contract) fun qux2() { + self.foo() + base.baz() + } + access(account) fun qux3() { + self.foo() + base.baz() + } + access(all) fun bar() { + self.qux1() + self.qux2() + self.qux3() + } + } + + access(all) fun main() { + var r <- attach A() to <- create R() + r[A]!.bar() + destroy r + } + `) + + _, err := inter.Invoke("main") + require.NoError(t, err) +} + func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() From c54caa3fd9f617e74355d09b03db662645e04c97 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Nov 2023 14:25:48 -0400 Subject: [PATCH 18/52] better fix --- runtime/interpreter/interpreter_expression.go | 10 ---------- runtime/sema/check_composite_declaration.go | 4 ++++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 8a16607921..20a18c0f2a 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -335,16 +335,6 @@ func (interpreter *Interpreter) checkMemberAccess( targetStaticType := target.StaticType(interpreter) - // if both the targetType and the expectedType are references, we unwrap them and instead compare their underlying type - // this is because the "real" type of the target's entitlements may not yet be populated until after the member access - // succeeds, leading to extraneous errors here. The entitlements are enforced instead at checking time. - if referenceStaticType, isReferenceStaticType := targetStaticType.(*ReferenceStaticType); isReferenceStaticType { - targetStaticType = referenceStaticType.ReferencedType - if referenceType, isReferenceType := expectedType.(*sema.ReferenceType); isReferenceType { - expectedType = referenceType.Type - } - } - if !interpreter.IsSubTypeOfSemaType(targetStaticType, expectedType) { targetSemaType := interpreter.MustConvertStaticToSemaType(targetStaticType) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index bd73227c47..b8011d9cdb 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2098,6 +2098,10 @@ func (checker *Checker) checkCompositeFunctions( defer checker.leaveValueScope(function.EndPosition, true) fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindComposite) + // all non-entitlement functions produce unauthorized references in attachments + if fnAccess.IsPrimitiveAccess() { + fnAccess = UnauthorizedAccess + } checker.declareSelfValue(fnAccess, selfType, selfDocString) if selfType.GetCompositeKind() == common.CompositeKindAttachment { From 941745e90f029d28afac011352a4602fd5f32e58 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 6 Nov 2023 11:07:37 -0500 Subject: [PATCH 19/52] fix crash on use of bound functions involving base inside constructor of attachment --- runtime/interpreter/interpreter.go | 1 + runtime/tests/interpreter/attachments_test.go | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index d86425c521..7dda9d673a 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1306,6 +1306,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( // set the base to the implicitly provided value, and remove this implicit argument from the list implicitArgumentPos := len(invocation.Arguments) - 1 invocation.Base = invocation.Arguments[implicitArgumentPos].(*EphemeralReferenceValue) + value.base = invocation.Base.Value.(*CompositeValue) invocation.Arguments[implicitArgumentPos] = nil invocation.Arguments = invocation.Arguments[:implicitArgumentPos] invocation.ArgumentTypes[implicitArgumentPos] = nil diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index d9225b51da..a624de5fae 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1984,7 +1984,6 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { value.(*interpreter.EphemeralReferenceValue).Value, ) }) - } func TestInterpretForEachAttachment(t *testing.T) { @@ -2122,6 +2121,49 @@ func TestInterpretForEachAttachment(t *testing.T) { AssertValuesEqual(t, inter, interpreter.NewUnmeteredIntValueFromInt64(0), value) }) + t.Run("bound function", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement F + + access(all) struct S { + access(F) let x: Int + init() { + self.x = 3 + } + } + access(all) attachment A for S { + access(F) var funcPtr: fun(): auth(F) ∬ + init() { + self.funcPtr = self.foo + } + access(F) fun foo(): auth(F) &Int { + return &base.x + } + } + fun test(): &Int { + let r = attach A() to S() + let rRef = &r as auth(F) &S + let a = rRef[A]! + let i = a.foo() + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + t.Run("access fields", func(t *testing.T) { t.Parallel() From 8b64a076ae5f44f61644c045b859bc6e4bab6c0b Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 6 Nov 2023 11:18:32 -0500 Subject: [PATCH 20/52] disable entitlement-mapped fields on attachments for now --- runtime/sema/checker.go | 10 +++++ runtime/sema/errors.go | 24 ++++++++++++ runtime/tests/checker/attachments_test.go | 38 +++++++++++-------- runtime/tests/checker/entitlements_test.go | 23 ++++++----- runtime/tests/interpreter/attachments_test.go | 25 ++++++++++-- 5 files changed, 92 insertions(+), 28 deletions(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 619a12087e..99f5888197 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1916,6 +1916,16 @@ func (checker *Checker) checkEntitlementMapAccess( return } + // due to potential security issues, entitlement mappings are disabled on attachments for now + if *containerKind == common.CompositeKindAttachment { + checker.report( + &InvalidAttachmentMappedEntitlementMemberError{ + Pos: startPos, + }, + ) + return + } + // mapped entitlement fields must be one of: // 1) An [optional] reference that is authorized to the same mapped entitlement. // 2) A function that return an [optional] reference authorized to the same mapped entitlement. diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index b40f3d8111..f6a47b236c 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4245,6 +4245,30 @@ func (e *InvalidMappedEntitlementMemberError) EndPosition(common.MemoryGauge) as return e.Pos } +// InvalidAttachmentMappedEntitlementMemberError +type InvalidAttachmentMappedEntitlementMemberError struct { + Pos ast.Position +} + +var _ SemanticError = &InvalidAttachmentMappedEntitlementMemberError{} +var _ errors.UserError = &InvalidAttachmentMappedEntitlementMemberError{} + +func (*InvalidAttachmentMappedEntitlementMemberError) isSemanticError() {} + +func (*InvalidAttachmentMappedEntitlementMemberError) IsUserError() {} + +func (e *InvalidAttachmentMappedEntitlementMemberError) Error() string { + return "entitlement mapped members are not yet supported on attachments" +} + +func (e *InvalidAttachmentMappedEntitlementMemberError) StartPosition() ast.Position { + return e.Pos +} + +func (e *InvalidAttachmentMappedEntitlementMemberError) EndPosition(common.MemoryGauge) ast.Position { + return e.Pos +} + // InvalidNonEntitlementAccessError type InvalidNonEntitlementAccessError struct { ast.Range diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 83e4e6766a..83ca5dbd22 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -3907,7 +3907,8 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("in base", func(t *testing.T) { @@ -3961,7 +3962,7 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { ) }) - t.Run("in base, with entitlements", func(t *testing.T) { + t.Run("identity mapping in attachment", func(t *testing.T) { t.Parallel() @@ -3982,7 +3983,8 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("in self, through base", func(t *testing.T) { @@ -4145,7 +4147,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function self value cast", func(t *testing.T) { @@ -4186,7 +4189,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function self value cast invalid access", func(t *testing.T) { @@ -4227,9 +4231,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + require.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function base value cast", func(t *testing.T) { @@ -4269,7 +4273,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function base value cast invalid access", func(t *testing.T) { @@ -4309,8 +4314,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function self value access", func(t *testing.T) { @@ -4348,8 +4354,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function base value access", func(t *testing.T) { @@ -4386,8 +4393,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) } diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index de83709179..95989fd790 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4760,7 +4760,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("invalid base and self in mapped functions", func(t *testing.T) { @@ -4785,10 +4786,11 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 3) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) + require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, "auth(Y) &S", typeMismatchErr.ExpectedType.QualifiedString(), @@ -4798,7 +4800,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ActualType.QualifiedString(), ) - require.ErrorAs(t, errs[1], &typeMismatchErr) + require.ErrorAs(t, errs[2], &typeMismatchErr) assert.Equal(t, "auth(Y) &A", typeMismatchErr.ExpectedType.QualifiedString(), @@ -4915,7 +4917,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("access(all) decl", func(t *testing.T) { @@ -5187,7 +5190,7 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { ) }) - t.Run("base attachment access in mapped function", func(t *testing.T) { + t.Run("mapped function in attachment", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -5208,7 +5211,8 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("invalid base attachment access in mapped function", func(t *testing.T) { @@ -5232,10 +5236,11 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) + require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, "auth(Y) &A?", typeMismatchErr.ExpectedType.QualifiedString(), diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index a624de5fae..aa1d6070a4 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/tests/checker" . "github.com/onflow/cadence/runtime/tests/utils" ) @@ -1893,7 +1894,7 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, _ := parseCheckAndInterpretWithOptions(t, ` entitlement E entitlement F entitlement G @@ -1924,7 +1925,15 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { destroy r return i } - `) + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) value, err := inter.Invoke("test") require.NoError(t, err) @@ -1942,7 +1951,7 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, _ := parseCheckAndInterpretWithOptions(t, ` entitlement E entitlement F entitlement mapping M { @@ -1971,7 +1980,15 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { destroy r return i } - `) + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) value, err := inter.Invoke("test") require.NoError(t, err) From 7248b9a56a50afe2f5b2f6c6baa35710953a2402 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 8 Nov 2023 12:05:33 -0500 Subject: [PATCH 21/52] fix tests --- runtime/tests/checker/events_test.go | 34 ++++------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 86d1b097fc..93fb69956d 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -1202,30 +1202,6 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { } } `) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) - }) - - t.Run("attachment with entitled base allowed", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - - attachment A for R { - require entitlement E - event ResourceDestroyed(name: Int = base.field) - } - - resource R { - access(E) let field : Int - - init() { - self.field = 3 - } - } - `) require.NoError(t, err) }) @@ -1236,11 +1212,7 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement E - entitlement mapping M { - E -> E - } - - access(mapping M) attachment A for R { + access(all) attachment A for R { access(E) let field : Int event ResourceDestroyed(name: Int = self.field) init() { @@ -1248,7 +1220,9 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { } } - resource R {} + resource R { + access(E) fun foo() {} + } `) require.NoError(t, err) }) From cb4f675cd0543ebd4c0560e6dd4bf4dff90fa597 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:43:59 -0500 Subject: [PATCH 22/52] don't get access from function type --- runtime/interpreter/interpreter.go | 12 ++++++------ runtime/interpreter/value.go | 3 ++- runtime/sema/check_composite_declaration.go | 4 ++-- runtime/sema/checker.go | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 66e580c0c5..cac1ade5e7 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4768,25 +4768,25 @@ func (interpreter *Interpreter) ReportComputation(compKind common.ComputationKin } } -func (interpreter *Interpreter) getAccessOfMember(self Value, identifier string) *sema.Access { +func (interpreter *Interpreter) getAccessOfMember(self Value, identifier string) sema.Access { typ, err := interpreter.ConvertStaticToSemaType(self.StaticType(interpreter)) // some values (like transactions) do not have types that can be looked up this way. These types // do not support entitled members, so their access is always unauthorized if err != nil { - return &sema.UnauthorizedAccess + return sema.UnauthorizedAccess } member, hasMember := typ.GetMembers()[identifier] // certain values (like functions) have builtin members that are not present on the type // in such cases the access is always unauthorized if !hasMember { - return &sema.UnauthorizedAccess + return sema.UnauthorizedAccess } - return &member.Resolve(interpreter, identifier, ast.EmptyRange, func(err error) {}).Access + return member.Resolve(interpreter, identifier, ast.EmptyRange, func(err error) {}).Access } func (interpreter *Interpreter) mapMemberValueAuthorization( self Value, - memberAccess *sema.Access, + memberAccess sema.Access, resultValue Value, resultingType sema.Type, ) Value { @@ -4795,7 +4795,7 @@ func (interpreter *Interpreter) mapMemberValueAuthorization( return resultValue } - if mappedAccess, isMappedAccess := (*memberAccess).(*sema.EntitlementMapAccess); isMappedAccess { + if mappedAccess, isMappedAccess := (memberAccess).(*sema.EntitlementMapAccess); isMappedAccess { var auth Authorization switch selfValue := self.(type) { case AuthorizedValue: diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 618b20533c..c5086c23d7 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16802,7 +16802,8 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var base *EphemeralReferenceValue var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { - functionAccess := function.FunctionType().Access + functionAccess := interpreter.getAccessOfMember(v, name) + // with respect to entitlements, any access inside an attachment that is not an entitlement access // does not provide any entitlements to base and self // E.g. consider: diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index b71db1f514..2c6197b7bd 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1579,7 +1579,7 @@ func (checker *Checker) memberSatisfied( // Check access effectiveInterfaceMemberAccess := checker.effectiveInterfaceMemberAccess(interfaceMember.Access) - effectiveCompositeMemberAccess := checker.effectiveCompositeMemberAccess(compositeMember.Access) + effectiveCompositeMemberAccess := EffectiveCompositeMemberAccess(compositeMember.Access, checker.Config.AccessCheckMode) return !effectiveCompositeMemberAccess.IsLessPermissiveThan(effectiveInterfaceMemberAccess) } @@ -1911,7 +1911,7 @@ func (checker *Checker) enumMembersAndOrigins( // Enum cases must be effectively public enumAccess := checker.accessFromAstAccess(enumCase.Access) - if !checker.effectiveCompositeMemberAccess(enumAccess).Equal(PrimitiveAccess(ast.AccessAll)) { + if !EffectiveCompositeMemberAccess(enumAccess, checker.Config.AccessCheckMode).Equal(PrimitiveAccess(ast.AccessAll)) { checker.report( &InvalidAccessModifierError{ DeclarationKind: enumCase.DeclarationKind(), diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 99f5888197..2d6e468674 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2368,7 +2368,7 @@ func (checker *Checker) TypeActivationDepth() int { func (checker *Checker) effectiveMemberAccess(access Access, containerKind ContainerKind) Access { switch containerKind { case ContainerKindComposite: - return checker.effectiveCompositeMemberAccess(access) + return EffectiveCompositeMemberAccess(access, checker.Config.AccessCheckMode) case ContainerKindInterface: return checker.effectiveInterfaceMemberAccess(access) default: @@ -2384,12 +2384,12 @@ func (checker *Checker) effectiveInterfaceMemberAccess(access Access) Access { } } -func (checker *Checker) effectiveCompositeMemberAccess(access Access) Access { +func EffectiveCompositeMemberAccess(access Access, checkMode AccessCheckMode) Access { if !access.Equal(PrimitiveAccess(ast.AccessNotSpecified)) { return access } - switch checker.Config.AccessCheckMode { + switch checkMode { case AccessCheckModeStrict, AccessCheckModeNotSpecifiedRestricted: return PrimitiveAccess(ast.AccessSelf) From 92c232333861b7d5925050344a5d7fa0088135d9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:51:00 -0500 Subject: [PATCH 23/52] remove parsing support for requiring entitlements --- runtime/ast/attachment.go | 59 +++------- runtime/ast/attachment_test.go | 84 +------------ runtime/interpreter/interpreter_expression.go | 8 -- runtime/interpreter/value.go | 8 -- runtime/parser/declaration.go | 49 -------- runtime/parser/declaration_test.go | 111 +----------------- runtime/sema/check_attach_expression.go | 13 -- runtime/sema/check_composite_declaration.go | 27 ----- runtime/sema/type.go | 1 - 9 files changed, 21 insertions(+), 339 deletions(-) diff --git a/runtime/ast/attachment.go b/runtime/ast/attachment.go index 397e1fa181..450c075493 100644 --- a/runtime/ast/attachment.go +++ b/runtime/ast/attachment.go @@ -29,13 +29,12 @@ import ( // AttachmentDeclaration type AttachmentDeclaration struct { - Access Access - Identifier Identifier - BaseType *NominalType - Conformances []*NominalType - RequiredEntitlements []*NominalType - Members *Members - DocString string + Access Access + Identifier Identifier + BaseType *NominalType + Conformances []*NominalType + Members *Members + DocString string Range } @@ -50,7 +49,6 @@ func NewAttachmentDeclaration( identifier Identifier, baseType *NominalType, conformances []*NominalType, - requiredEntitlements []*NominalType, members *Members, docString string, declarationRange Range, @@ -58,14 +56,13 @@ func NewAttachmentDeclaration( common.UseMemory(memoryGauge, common.AttachmentDeclarationMemoryUsage) return &AttachmentDeclaration{ - Access: access, - Identifier: identifier, - BaseType: baseType, - Conformances: conformances, - RequiredEntitlements: requiredEntitlements, - Members: members, - DocString: docString, - Range: declarationRange, + Access: access, + Identifier: identifier, + BaseType: baseType, + Conformances: conformances, + Members: members, + DocString: docString, + Range: declarationRange, } } @@ -113,10 +110,6 @@ func (d *AttachmentDeclaration) ConformanceList() []*NominalType { return d.Conformances } -func (d *AttachmentDeclaration) RequiredEntitlementsToAttach() []*NominalType { - return d.RequiredEntitlements -} - const attachmentStatementDoc = prettier.Text("attachment") const attachmentStatementForDoc = prettier.Text("for") const attachmentConformancesSeparatorDoc = prettier.Text(":") @@ -151,31 +144,7 @@ func (d *AttachmentDeclaration) Doc() prettier.Doc { ) var membersDoc prettier.Concat - if d.RequiredEntitlements != nil && len(d.RequiredEntitlements) > 0 { - membersDoc = append(membersDoc, membersStartDoc) - for _, entitlement := range d.RequiredEntitlements { - var entitlementRequiredDoc = prettier.Indent{ - Doc: prettier.Concat{ - attachmentRequireDoc, - prettier.Space, - attachmentEntitlementDoc, - prettier.Space, - entitlement.Doc(), - }, - } - membersDoc = append( - membersDoc, - prettier.HardLine{}, - entitlementRequiredDoc, - ) - } - if len(d.Members.declarations) > 0 { - membersDoc = append(membersDoc, prettier.HardLine{}, d.Members.docWithNoBraces()) - } - membersDoc = append(membersDoc, prettier.HardLine{}, membersEndDoc) - } else { - membersDoc = append(membersDoc, prettier.Line{}, d.Members.Doc()) - } + membersDoc = append(membersDoc, prettier.Line{}, d.Members.Doc()) if len(d.Conformances) > 0 { conformancesDoc := prettier.Concat{ diff --git a/runtime/ast/attachment_test.go b/runtime/ast/attachment_test.go index 65d6faf50b..c42a27afae 100644 --- a/runtime/ast/attachment_test.go +++ b/runtime/ast/attachment_test.go @@ -56,22 +56,6 @@ func TestAttachmentDeclaration_MarshallJSON(t *testing.T) { ), }, }, - RequiredEntitlements: []*NominalType{ - { - Identifier: NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - { - Identifier: NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - }, Members: NewMembers(nil, []Declaration{}), DocString: "test", Range: Range{ @@ -118,28 +102,6 @@ func TestAttachmentDeclaration_MarshallJSON(t *testing.T) { "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 3, "Line": 2, "Column": 5} } - ], - "RequiredEntitlements": [ - { - "Type": "NominalType", - "Identifier": { - "Identifier": "X", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - { - "Type": "NominalType", - "Identifier": { - "Identifier": "Y", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - } ], "Members": { "Declarations": [] @@ -179,22 +141,6 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { ), }, }, - RequiredEntitlements: []*NominalType{ - { - Identifier: NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - { - Identifier: NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - }, - }, Members: NewMembers(nil, []Declaration{}), DocString: "test", Range: Range{ @@ -225,29 +171,8 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { Doc: prettier.Concat{ prettier.Line{}, prettier.Concat{ - prettier.Text("{"), - prettier.HardLine{}, - prettier.Indent{ - Doc: prettier.Concat{ - prettier.Text("require"), - prettier.Text(" "), - prettier.Text("entitlement"), - prettier.Text(" "), - prettier.Text("X"), - }, - }, - prettier.HardLine{}, - prettier.Indent{ - Doc: prettier.Concat{ - prettier.Text("require"), - prettier.Text(" "), - prettier.Text("entitlement"), - prettier.Text(" "), - prettier.Text("Y"), - }, - }, - prettier.HardLine{}, - prettier.Text("}"), + prettier.Line{}, + prettier.Text("{}"), }, }, }, @@ -260,10 +185,7 @@ func TestAttachmentDeclaration_Doc(t *testing.T) { require.Equal(t, `access(all) -attachment Foo for Bar: Baz { -require entitlement X -require entitlement Y -}`, +attachment Foo for Bar: Baz {}`, decl.String(), ) } diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 35d4a9f932..7575e9b1e9 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1407,14 +1407,6 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta var auth Authorization = UnauthorizedAccess attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess := sema.EntitlementSetAccess{ - SetKind: sema.Conjunction, - Entitlements: attachmentType.RequiredEntitlements, - } - auth = ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) - } - var baseValue Value = NewEphemeralReferenceValue( interpreter, auth, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 44fc306c42..871b4af6ba 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17796,14 +17796,6 @@ func attachmentBaseAuthorization( attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess - attachmentType := interpreter.MustSemaTypeOfValue(attachment).(*sema.CompositeType) - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess := sema.EntitlementSetAccess{ - SetKind: sema.Conjunction, - Entitlements: attachmentType.RequiredEntitlements, - } - auth = ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) - } return auth } diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index 5a3353f397..c3de31689c 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -1344,47 +1344,6 @@ func parseCompositeOrInterfaceDeclaration( } } -func parseRequiredEntitlement(p *parser) (*ast.NominalType, error) { - if !p.isToken(p.current, lexer.TokenIdentifier, KeywordRequire) { - return nil, p.syntaxError( - "expected 'require', got %s", - p.current.Type, - ) - } - - // skip the `require` keyword - p.nextSemanticToken() - - if !p.isToken(p.current, lexer.TokenIdentifier, KeywordEntitlement) { - return nil, p.syntaxError( - "expected 'entitlement', got %s", - p.current.Type, - ) - } - - // skip the `entitlement` keyword - p.nextSemanticToken() - - return rejectAccessKeywords(p, func() (*ast.NominalType, error) { - return parseNominalType(p, lowestBindingPower) - }) -} - -func parseRequiredEntitlements(p *parser) ([]*ast.NominalType, error) { - var requiredEntitlements []*ast.NominalType - - for p.isToken(p.current, lexer.TokenIdentifier, KeywordRequire) { - requiredEntitlement, err := parseRequiredEntitlement(p) - if err != nil { - return nil, err - } - requiredEntitlements = append(requiredEntitlements, requiredEntitlement) - p.skipSpaceAndComments() - } - - return requiredEntitlements, nil -} - func parseAttachmentDeclaration( p *parser, access ast.Access, @@ -1450,13 +1409,6 @@ func parseAttachmentDeclaration( p.skipSpaceAndComments() - requiredEntitlements, err := parseRequiredEntitlements(p) - if err != nil { - return nil, err - } - - p.skipSpaceAndComments() - members, err := parseMembersAndNestedDeclarations(p, lexer.TokenBraceClose) if err != nil { return nil, err @@ -1481,7 +1433,6 @@ func parseAttachmentDeclaration( identifier, baseNominalType, conformances, - requiredEntitlements, members, docString, declarationRange, diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 7f9795c4af..27da105bc2 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -4174,120 +4174,17 @@ func TestParseAttachmentDeclaration(t *testing.T) { ) }) - t.Run("required entitlements", func(t *testing.T) { - - t.Parallel() - - result, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement X - require entitlement Y - }`) - require.Empty(t, errs) - - utils.AssertEqualWithDiff(t, - []ast.Declaration{ - &ast.AttachmentDeclaration{ - Access: ast.AccessAll, - Identifier: ast.Identifier{ - Identifier: "E", - Pos: ast.Position{ - Offset: 23, - Line: 1, - Column: 23, - }, - }, - BaseType: &ast.NominalType{ - Identifier: ast.Identifier{ - Identifier: "S", - Pos: ast.Position{ - Offset: 29, - Line: 1, - Column: 29, - }, - }, - }, - RequiredEntitlements: []*ast.NominalType{ - { - Identifier: ast.Identifier{ - Identifier: "X", - Pos: ast.Position{ - Offset: 56, - Line: 2, - Column: 23, - }, - }, - }, - { - Identifier: ast.Identifier{ - Identifier: "Y", - Pos: ast.Position{ - Offset: 81, - Line: 3, - Column: 23, - }, - }, - }, - }, - Members: ast.NewUnmeteredMembers(nil), - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 0, - Line: 1, - Column: 0, - }, - EndPos: ast.Position{ - Offset: 85, - Line: 4, - Column: 2, - }, - }, - }, - }, - result, - ) - }) - - t.Run("required entitlements error no identifier", func(t *testing.T) { + t.Run("required entitlements error", func(t *testing.T) { t.Parallel() _, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement - }`) - utils.AssertEqualWithDiff(t, []error{ - &SyntaxError{ - Pos: ast.Position{Line: 3, Column: 3, Offset: 60}, - Message: "unexpected token in type: '}'", - }, - }, errs) - }) - - t.Run("required entitlements error no entitlement", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseDeclarations(`access(all) attachment E for S { - require X - }`) - utils.AssertEqualWithDiff(t, []error{ - &SyntaxError{ - Pos: ast.Position{Line: 2, Column: 11, Offset: 44}, - Message: "expected 'entitlement', got identifier", - }, - }, errs) - }) - - t.Run("required entitlements error non-nominal type", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseDeclarations(`access(all) attachment E for S { - require entitlement [X] + require entitlement X }`) utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ - Pos: ast.Position{Line: 2, Column: 26, Offset: 59}, - Message: "unexpected non-nominal type: [X]", + Pos: ast.Position{Line: 2, Column: 3, Offset: 36}, + Message: "unexpected identifier", }, }, errs) }) diff --git a/runtime/sema/check_attach_expression.go b/runtime/sema/check_attach_expression.go index 9dd401c8bc..55e13a3669 100644 --- a/runtime/sema/check_attach_expression.go +++ b/runtime/sema/check_attach_expression.go @@ -127,18 +127,5 @@ func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) }) } - // if the attachment requires entitlements, check that they are provided as requested - if attachmentCompositeType.RequiredEntitlements != nil { - attachmentCompositeType.RequiredEntitlements.Foreach(func(key *EntitlementType, _ struct{}) { - if !providedEntitlements.Contains(key) { - checker.report(&RequiredEntitlementNotProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, expression), - AttachmentType: attachmentCompositeType, - RequiredEntitlement: key, - }) - } - }) - } - return baseType } diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 8267d69b88..8cf4b210f9 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -638,27 +638,6 @@ func (checker *Checker) declareAttachmentType(declaration *ast.AttachmentDeclara composite.AttachmentEntitlementAccess = attachmentAccess } - // add all the required entitlements to a set for this attachment - requiredEntitlements := orderedmap.New[EntitlementOrderedSet](len(declaration.RequiredEntitlements)) - for _, entitlement := range declaration.RequiredEntitlements { - nominalType := checker.convertNominalType(entitlement) - if entitlementType, isEntitlement := nominalType.(*EntitlementType); isEntitlement { - _, present := requiredEntitlements.Set(entitlementType, struct{}{}) - if present { - checker.report(&DuplicateEntitlementRequirementError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - Entitlement: entitlementType, - }) - } - continue - } - checker.report(&InvalidNonEntitlementRequirement{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - InvalidType: nominalType, - }) - } - composite.RequiredEntitlements = requiredEntitlements - return composite } @@ -2370,12 +2349,6 @@ func (checker *Checker) declareBaseValue(baseType Type, attachmentType *Composit // ------------------------------- // within the body of `foo`, the `base` value will be entitled to `E` but not `F`, because only `E` was required in the attachment's declaration var baseAccess Access = UnauthorizedAccess - if attachmentType.RequiredEntitlements.Len() > 0 { - baseAccess = EntitlementSetAccess{ - Entitlements: attachmentType.RequiredEntitlements, - SetKind: Conjunction, - } - } base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 216e1fc9e0..ea9e39e33c 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4294,7 +4294,6 @@ type CompositeType struct { // Alas, this is Go, so for now these fields are only non-nil when Kind is CompositeKindAttachment baseType Type baseTypeDocString string - RequiredEntitlements *EntitlementOrderedSet AttachmentEntitlementAccess *EntitlementMapAccess DefaultDestroyEvent *CompositeType From a6eb52620654d28c3aff69ac9b282d2a94f81dc9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 13:24:26 -0400 Subject: [PATCH 24/52] remove support for providing entitlements during attach --- runtime/ast/attachment.go | 33 +- runtime/ast/attachment_test.go | 73 +--- runtime/contract_update_validation_test.go | 135 ------- runtime/parser/expression.go | 32 +- runtime/parser/expression_test.go | 87 +--- runtime/parser/keyword.go | 2 - runtime/sema/check_attach_expression.go | 21 - runtime/sema/errors.go | 88 ----- runtime/stdlib/contract_update_validation.go | 62 --- runtime/tests/checker/entitlements_test.go | 394 ------------------- 10 files changed, 13 insertions(+), 914 deletions(-) diff --git a/runtime/ast/attachment.go b/runtime/ast/attachment.go index 450c075493..c16005db4d 100644 --- a/runtime/ast/attachment.go +++ b/runtime/ast/attachment.go @@ -113,8 +113,6 @@ func (d *AttachmentDeclaration) ConformanceList() []*NominalType { const attachmentStatementDoc = prettier.Text("attachment") const attachmentStatementForDoc = prettier.Text("for") const attachmentConformancesSeparatorDoc = prettier.Text(":") -const attachmentEntitlementDoc = prettier.Text("entitlement") -const attachmentRequireDoc = prettier.Text("require") var attachmentConformanceSeparatorDoc prettier.Doc = prettier.Concat{ prettier.Text(","), @@ -213,10 +211,9 @@ func (d *AttachmentDeclaration) String() string { // AttachExpression type AttachExpression struct { - Base Expression - Attachment *InvocationExpression - Entitlements []*NominalType - StartPos Position `json:"-"` + Base Expression + Attachment *InvocationExpression + StartPos Position `json:"-"` } var _ Element = &AttachExpression{} @@ -239,16 +236,14 @@ func NewAttachExpression( gauge common.MemoryGauge, base Expression, attachment *InvocationExpression, - entitlements []*NominalType, startPos Position, ) *AttachExpression { common.UseMemory(gauge, common.AttachExpressionMemoryUsage) return &AttachExpression{ - Base: base, - Attachment: attachment, - Entitlements: entitlements, - StartPos: startPos, + Base: base, + Attachment: attachment, + StartPos: startPos, } } @@ -258,8 +253,6 @@ func (e *AttachExpression) String() string { const attachExpressionDoc = prettier.Text("attach") const attachExpressionToDoc = prettier.Text("to") -const attachExpressionWithDoc = prettier.Text("with") -const attachExpressionCommaDoc = prettier.Text(",") func (e *AttachExpression) Doc() prettier.Doc { var doc prettier.Concat @@ -274,17 +267,6 @@ func (e *AttachExpression) Doc() prettier.Doc { prettier.Space, e.Base.Doc(), ) - if len(e.Entitlements) > 0 { - entitlementsLen := len(e.Entitlements) - doc = append(doc, prettier.Space, attachExpressionWithDoc, prettier.Space, openParenthesisDoc) - for i, entitlement := range e.Entitlements { - doc = append(doc, entitlement.Doc()) - if i < entitlementsLen-1 { - doc = append(doc, attachExpressionCommaDoc, prettier.Space) - } - } - doc = append(doc, closeParenthesisDoc) - } return doc } @@ -293,9 +275,6 @@ func (e *AttachExpression) StartPosition() Position { } func (e *AttachExpression) EndPosition(memoryGauge common.MemoryGauge) Position { - if len(e.Entitlements) > 0 { - return e.Entitlements[len(e.Entitlements)-1].EndPosition(memoryGauge) - } return e.Base.EndPosition(memoryGauge) } diff --git a/runtime/ast/attachment_test.go b/runtime/ast/attachment_test.go index c42a27afae..35d27e119a 100644 --- a/runtime/ast/attachment_test.go +++ b/runtime/ast/attachment_test.go @@ -218,24 +218,6 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { Position{Offset: 1, Line: 2, Column: 3}, Position{Offset: 1, Line: 2, Column: 3}, ), - Entitlements: []*NominalType{ - NewNominalType(nil, - NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - NewNominalType(nil, - NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - }, StartPos: Position{Offset: 1, Line: 2, Column: 3}, } @@ -248,7 +230,7 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { { "Type": "AttachExpression", "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3}, + "EndPos": {"Offset": 3, "Line": 2, "Column": 5}, "Base": { "Type": "IdentifierExpression", "Identifier": { @@ -276,29 +258,7 @@ func TestAttachExpressionMarshallJSON(t *testing.T) { "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, "ArgumentsStartPos": {"Offset": 1, "Line": 2, "Column": 3}, "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "Entitlements": [ - { - "Type": "NominalType", - "Identifier": { - "Identifier": "X", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - { - "Type": "NominalType", - "Identifier": { - "Identifier": "Y", - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - }, - "StartPos": {"Offset": 1, "Line": 2, "Column": 3}, - "EndPos": {"Offset": 1, "Line": 2, "Column": 3} - } - ] + } } `, string(actual), @@ -333,24 +293,6 @@ func TestAttachExpression_Doc(t *testing.T) { Position{Offset: 1, Line: 2, Column: 3}, Position{Offset: 1, Line: 2, Column: 3}, ), - Entitlements: []*NominalType{ - NewNominalType(nil, - NewIdentifier( - nil, - "X", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - NewNominalType(nil, - NewIdentifier( - nil, - "Y", - Position{Offset: 1, Line: 2, Column: 3}, - ), - []Identifier{}, - ), - }, StartPos: Position{Offset: 1, Line: 2, Column: 3}, } @@ -367,20 +309,11 @@ func TestAttachExpression_Doc(t *testing.T) { prettier.Text("to"), prettier.Text(" "), prettier.Text("foo"), - prettier.Text(" "), - prettier.Text("with"), - prettier.Text(" "), - prettier.Text("("), - prettier.Text("X"), - prettier.Text(","), - prettier.Text(" "), - prettier.Text("Y"), - prettier.Text(")"), }, decl.Doc(), ) - require.Equal(t, "attach bar() to foo with (X, Y)", decl.String()) + require.Equal(t, "attach bar() to foo", decl.String()) } func TestRemoveStatement_MarshallJSON(t *testing.T) { diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index 0617b62b6e..dcf8c5259a 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -1869,17 +1869,6 @@ func assertConformanceMismatchError( assert.Equal(t, erroneousDeclName, conformanceMismatchError.DeclName) } -func assertEntitlementRequirementMismatchError( - t *testing.T, - err error, - erroneousDeclName string, -) { - var entitlementMismatchError *stdlib.RequiredEntitlementMismatchError - require.ErrorAs(t, err, &entitlementMismatchError) - - assert.Equal(t, erroneousDeclName, entitlementMismatchError.DeclName) -} - func assertEnumCaseMismatchError(t *testing.T, err error, expectedEnumCase string, foundEnumCase string) { var enumMismatchError *stdlib.EnumCaseMismatchError require.ErrorAs(t, err, &enumMismatchError) @@ -2108,130 +2097,6 @@ func TestRuntimeContractUpdateConformanceChanges(t *testing.T) { require.NoError(t, err) }) - t.Run("removing required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - require.NoError(t, err) - }) - - t.Run("reordering required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement Y - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - require.NoError(t, err) - }) - - t.Run("renaming required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement Y - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - RequireError(t, err) - - cause := getSingleContractUpdateErrorCause(t, err, "Test") - - assertEntitlementRequirementMismatchError(t, cause, "Foo") - }) - - t.Run("adding required entitlement", func(t *testing.T) { - - t.Parallel() - - const oldCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - } - } - ` - - const newCode = ` - access(all) contract Test { - access(all) entitlement X - access(all) entitlement Y - access(all) attachment Foo for AnyStruct { - require entitlement X - require entitlement Y - } - } - ` - - err := testDeployAndUpdate(t, "Test", oldCode, newCode) - RequireError(t, err) - - cause := getSingleContractUpdateErrorCause(t, err, "Test") - - assertEntitlementRequirementMismatchError(t, cause, "Foo") - }) - t.Run("missing comma in parameter list of old contract", func(t *testing.T) { t.Parallel() diff --git a/runtime/parser/expression.go b/runtime/parser/expression.go index b9d1b5c8e5..9d06d9def6 100644 --- a/runtime/parser/expression.go +++ b/runtime/parser/expression.go @@ -971,37 +971,7 @@ func parseAttachExpressionRemainder(p *parser, token lexer.Token) (*ast.AttachEx p.skipSpaceAndComments() - var entitlements []*ast.NominalType - if p.isToken(p.current, lexer.TokenIdentifier, KeywordWith) { - // consume the `with` token - p.nextSemanticToken() - - _, err = p.mustOne(lexer.TokenParenOpen) - if err != nil { - return nil, err - } - - entitlements, _, err = parseNominalTypes(p, lexer.TokenParenClose, lexer.TokenComma) - for _, entitlement := range entitlements { - _, err = rejectAccessKeywords(p, func() (*ast.NominalType, error) { - return entitlement, nil - }) - if err != nil { - return nil, err - } - } - if err != nil { - return nil, err - } - - _, err = p.mustOne(lexer.TokenParenClose) - if err != nil { - return nil, err - } - p.skipSpaceAndComments() - } - - return ast.NewAttachExpression(p.memoryGauge, base, attachment, entitlements, token.StartPos), nil + return ast.NewAttachExpression(p.memoryGauge, base, attachment, token.StartPos), nil } // Invocation Expression Grammar: diff --git a/runtime/parser/expression_test.go b/runtime/parser/expression_test.go index ac382ca8bb..b3c27caa96 100644 --- a/runtime/parser/expression_test.go +++ b/runtime/parser/expression_test.go @@ -2843,93 +2843,12 @@ func TestParseAttach(t *testing.T) { t.Parallel() - result, errs := testParseExpression("attach E() to r with (X, Y)") - require.Empty(t, errs) - - utils.AssertEqualWithDiff(t, - &ast.AttachExpression{ - Base: &ast.IdentifierExpression{ - Identifier: ast.Identifier{ - Identifier: "r", - Pos: ast.Position{Line: 1, Column: 14, Offset: 14}, - }, - }, - Attachment: &ast.InvocationExpression{ - InvokedExpression: &ast.IdentifierExpression{ - Identifier: ast.Identifier{ - Identifier: "E", - Pos: ast.Position{Line: 1, Column: 7, Offset: 7}, - }, - }, - ArgumentsStartPos: ast.Position{Line: 1, Column: 8, Offset: 8}, - EndPos: ast.Position{Line: 1, Column: 9, Offset: 9}, - }, - Entitlements: []*ast.NominalType{ - ast.NewNominalType( - nil, - ast.Identifier{ - Identifier: "X", - Pos: ast.Position{Line: 1, Column: 22, Offset: 22}, - }, - nil, - ), - ast.NewNominalType( - nil, - ast.Identifier{ - Identifier: "Y", - Pos: ast.Position{Line: 1, Column: 25, Offset: 25}, - }, - nil, - ), - }, - StartPos: ast.Position{Line: 1, Column: 0, Offset: 0}, - }, - result, - ) - }) - - t.Run("with provided entitlements not closed", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with (X, Y") + _, errs := testParseExpression("attach E() to r with (X)") utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ - Message: "invalid end of input, expected ')'", - Pos: ast.Position{Offset: 26, Line: 1, Column: 26}, - }, - }, - errs, - ) - }) - - t.Run("with provided entitlements extra comma", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with (X, Y,)") - utils.AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "missing type after separator", - Pos: ast.Position{Offset: 27, Line: 1, Column: 27}, - }, - }, - errs, - ) - }) - - t.Run("with provided entitlements unopened", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseExpression("attach E() to r with X, Y)") - utils.AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "expected token '('", - Pos: ast.Position{Offset: 21, Line: 1, Column: 21}, + Message: "unexpected token: identifier", + Pos: ast.Position{Offset: 16, Line: 1, Column: 16}, }, }, errs, diff --git a/runtime/parser/keyword.go b/runtime/parser/keyword.go index 00e7dc9413..605b4e9e32 100644 --- a/runtime/parser/keyword.go +++ b/runtime/parser/keyword.go @@ -69,7 +69,6 @@ const ( keywordAttach = "attach" keywordRemove = "remove" keywordTo = "to" - KeywordWith = "with" KeywordRequire = "require" KeywordStatic = "static" KeywordNative = "native" @@ -122,7 +121,6 @@ var allKeywords = []string{ KeywordDefault, KeywordEnum, KeywordView, - KeywordWith, KeywordMapping, KeywordRequire, keywordAttach, diff --git a/runtime/sema/check_attach_expression.go b/runtime/sema/check_attach_expression.go index 55e13a3669..e2b70676cf 100644 --- a/runtime/sema/check_attach_expression.go +++ b/runtime/sema/check_attach_expression.go @@ -21,7 +21,6 @@ package sema import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/common/orderedmap" ) func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) Type { @@ -107,25 +106,5 @@ func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) checker.Elaboration.SetAttachTypes(expression, attachmentCompositeType) - // compute the set of all the entitlements provided to this attachment - providedEntitlements := orderedmap.New[EntitlementOrderedSet](len(expression.Entitlements)) - for _, entitlement := range expression.Entitlements { - nominalType := checker.convertNominalType(entitlement) - if entitlementType, isEntitlement := nominalType.(*EntitlementType); isEntitlement { - _, present := providedEntitlements.Set(entitlementType, struct{}{}) - if present { - checker.report(&DuplicateEntitlementProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - Entitlement: entitlementType, - }) - } - continue - } - checker.report(&InvalidNonEntitlementProvidedError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, entitlement), - InvalidType: nominalType, - }) - } - return baseType } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 78dff438f1..a0257cf331 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4376,94 +4376,6 @@ func (e *CyclicEntitlementMappingError) Error() string { ) } -type DuplicateEntitlementRequirementError struct { - Entitlement *EntitlementType - ast.Range -} - -var _ SemanticError = &DuplicateEntitlementRequirementError{} -var _ errors.UserError = &DuplicateEntitlementRequirementError{} - -func (*DuplicateEntitlementRequirementError) isSemanticError() {} - -func (*DuplicateEntitlementRequirementError) IsUserError() {} - -func (e *DuplicateEntitlementRequirementError) Error() string { - return fmt.Sprintf("entitlement %s is already required by this attachment", e.Entitlement.QualifiedString()) -} - -type DuplicateEntitlementProvidedError struct { - Entitlement *EntitlementType - ast.Range -} - -var _ SemanticError = &DuplicateEntitlementProvidedError{} -var _ errors.UserError = &DuplicateEntitlementProvidedError{} - -func (*DuplicateEntitlementProvidedError) isSemanticError() {} - -func (*DuplicateEntitlementProvidedError) IsUserError() {} - -func (e *DuplicateEntitlementProvidedError) Error() string { - return fmt.Sprintf("entitlement %s is already provided to this attachment", e.Entitlement.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type InvalidNonEntitlementRequirement struct { - InvalidType Type - ast.Range -} - -var _ SemanticError = &InvalidNonEntitlementRequirement{} -var _ errors.UserError = &InvalidNonEntitlementRequirement{} - -func (*InvalidNonEntitlementRequirement) isSemanticError() {} - -func (*InvalidNonEntitlementRequirement) IsUserError() {} - -func (e *InvalidNonEntitlementRequirement) Error() string { - return fmt.Sprintf("cannot use %s as an entitlement requirement", e.InvalidType.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type InvalidNonEntitlementProvidedError struct { - InvalidType Type - ast.Range -} - -var _ SemanticError = &InvalidNonEntitlementProvidedError{} -var _ errors.UserError = &InvalidNonEntitlementProvidedError{} - -func (*InvalidNonEntitlementProvidedError) isSemanticError() {} - -func (*InvalidNonEntitlementProvidedError) IsUserError() {} - -func (e *InvalidNonEntitlementProvidedError) Error() string { - return fmt.Sprintf("cannot provide %s as an entitlement to this attachment", e.InvalidType.QualifiedString()) -} - -// InvalidNonEntitlementRequirement -type RequiredEntitlementNotProvidedError struct { - RequiredEntitlement *EntitlementType - AttachmentType *CompositeType - ast.Range -} - -var _ SemanticError = &RequiredEntitlementNotProvidedError{} -var _ errors.UserError = &RequiredEntitlementNotProvidedError{} - -func (*RequiredEntitlementNotProvidedError) isSemanticError() {} - -func (*RequiredEntitlementNotProvidedError) IsUserError() {} - -func (e *RequiredEntitlementNotProvidedError) Error() string { - return fmt.Sprintf( - "attachment type `%s` requires entitlement `%s` to be provided when attaching", - e.AttachmentType.QualifiedString(), - e.RequiredEntitlement.QualifiedString(), - ) -} - // InvalidBaseTypeError type InvalidBaseTypeError struct { diff --git a/runtime/stdlib/contract_update_validation.go b/runtime/stdlib/contract_update_validation.go index 39f0d5edff..4c7502a27e 100644 --- a/runtime/stdlib/contract_update_validation.go +++ b/runtime/stdlib/contract_update_validation.go @@ -147,12 +147,6 @@ func (validator *ContractUpdateValidator) checkDeclarationUpdatability( validator.checkConformances(oldDecl, newDecl) } } - - if newDecl, ok := newDeclaration.(*ast.AttachmentDeclaration); ok { - if oldDecl, ok := oldDeclaration.(*ast.AttachmentDeclaration); ok { - validator.checkRequiredEntitlements(oldDecl, newDecl) - } - } } func (validator *ContractUpdateValidator) checkFields(oldDeclaration ast.Declaration, newDeclaration ast.Declaration) { @@ -338,47 +332,6 @@ func (validator *ContractUpdateValidator) checkEnumCases(oldDeclaration ast.Decl } } -func (validator *ContractUpdateValidator) checkRequiredEntitlements( - oldDecl *ast.AttachmentDeclaration, - newDecl *ast.AttachmentDeclaration, -) { - oldEntitlements := oldDecl.RequiredEntitlements - newEntitlements := newDecl.RequiredEntitlements - - // updates cannot add new entitlement requirements, or equivalently, - // the new entitlements must all be present in the old entitlements list - // Adding new entitlement requirements has to be prohibited because it would - // be a security vulnerability. If your attachment previously only requires X access to the base, - // people who might be okay giving an attachment X access to their resource would be willing to attach it. - // If the author could later add a requirement to the attachment declaration asking for Y access as well, - // then they would be able to access Y-entitled values on existing attached bases without ever having - // received explicit permission from the resource owners to access that entitlement. - - for _, newEntitlement := range newEntitlements { - found := false - for index, oldEntitlement := range oldEntitlements { - err := oldEntitlement.CheckEqual(newEntitlement, validator) - if err == nil { - found = true - - // Remove the matched entitlement, so we don't have to check it again. - // i.e: optimization - oldEntitlements = append(oldEntitlements[:index], oldEntitlements[index+1:]...) - break - } - } - - if !found { - validator.report(&RequiredEntitlementMismatchError{ - DeclName: newDecl.Identifier.Identifier, - Range: ast.NewUnmeteredRangeFromPositioned(newDecl.Identifier), - }) - - return - } - } -} - func (validator *ContractUpdateValidator) checkConformances( oldDecl *ast.CompositeDeclaration, newDecl *ast.CompositeDeclaration, @@ -595,21 +548,6 @@ func (e *ConformanceMismatchError) Error() string { return fmt.Sprintf("conformances does not match in `%s`", e.DeclName) } -// RequiredEntitlementMismatchError is reported during a contract update, when the required entitlements of the new attachment -// does not match the existing one. -type RequiredEntitlementMismatchError struct { - DeclName string - ast.Range -} - -var _ errors.UserError = &RequiredEntitlementMismatchError{} - -func (*RequiredEntitlementMismatchError) IsUserError() {} - -func (e *RequiredEntitlementMismatchError) Error() string { - return fmt.Sprintf("required entitlements do not match in `%s`", e.DeclName) -} - // EnumCaseMismatchError is reported during an enum update, when an updated enum case // does not match the existing enum case. type EnumCaseMismatchError struct { diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index f95dab90fa..43be13a3a0 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -5782,400 +5782,6 @@ func TestCheckEntitledWriteAndMutateNotAllowed(t *testing.T) { }) } -func TestCheckAttachmentRequireEntitlements(t *testing.T) { - t.Parallel() - - t.Run("entitlements allowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - attachment A for AnyStruct { - require entitlement E - require entitlement F - } - `) - - assert.NoError(t, err) - }) - - t.Run("entitlement mapping disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement mapping M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("event disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - event M() - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("struct disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("struct interface disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct interface M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("resource disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - resource M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("resource interface disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - resource interface M {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("attachment disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment M for AnyResource {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("enum disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - enum M: UInt8 {} - attachment A for AnyStruct { - require entitlement E - require entitlement M - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("int disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment A for AnyStruct { - require entitlement E - require entitlement Int - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.InvalidNonEntitlementRequirement{}, errs[0]) - }) - - t.Run("duplicates disallowed", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - attachment A for AnyStruct { - require entitlement E - require entitlement E - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.DuplicateEntitlementRequirementError{}, errs[0]) - }) -} - -func TestCheckAttachProvidedEntitlements(t *testing.T) { - t.Parallel() - - t.Run("all provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, F) - } - - `) - assert.NoError(t, err) - }) - - t.Run("extra provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement G - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, F, G) - } - - `) - assert.NoError(t, err) - }) - - t.Run("one missing", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E) - } - - `) - errs := RequireCheckerErrors(t, err, 1) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("one missing with extra provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement G - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() with (E, G) - } - - `) - errs := RequireCheckerErrors(t, err, 1) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("two missing", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct S {} - attachment A for S { - require entitlement E - require entitlement F - } - fun foo() { - let s = attach A() to S() - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[0], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "F", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("mapping provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement mapping M {} - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (M) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("int provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (UInt8) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) - - t.Run("struct provided", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - struct S {} - attachment A for S { - require entitlement E - } - fun foo() { - let s = attach A() to S() with (S) - } - - `) - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvalidNonEntitlementProvidedError{}, errs[0]) - - var requiredEntitlementNotProvidedErr *sema.RequiredEntitlementNotProvidedError - require.ErrorAs(t, errs[1], &requiredEntitlementNotProvidedErr) - assert.Equal(t, - "E", - requiredEntitlementNotProvidedErr.RequiredEntitlement.Identifier, - ) - }) -} - func TestCheckBuiltinEntitlements(t *testing.T) { t.Parallel() From 527c9d0c07e221d7cf002bbfdef8d3bbe02efd84 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 13:58:20 -0400 Subject: [PATCH 25/52] remove support for declaring attachments with entitlement maps --- runtime/interpreter/interpreter.go | 9 +- runtime/interpreter/value.go | 21 +--- runtime/sema/access.go | 10 ++ runtime/sema/check_composite_declaration.go | 109 ++++++-------------- runtime/sema/checker.go | 9 +- runtime/sema/errors.go | 21 ++-- runtime/sema/type.go | 30 +++--- runtime/tests/checker/entitlements_test.go | 16 +-- 8 files changed, 83 insertions(+), 142 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 9e8150aabf..0f13c34235 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1351,9 +1351,12 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( // Self's type in the constructor is codomain of the attachment's entitlement map, since // the constructor can only be called when in possession of the base resource // if the attachment is declared with access(all) access, then self is unauthorized - if attachmentType.AttachmentEntitlementAccess != nil { - auth = ConvertSemaAccessToStaticAuthorization(interpreter, attachmentType.AttachmentEntitlementAccess.Codomain()) - } + + auth = ConvertSemaAccessToStaticAuthorization( + interpreter, + sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction), + ) + self = NewEphemeralReferenceValue(interpreter, auth, value, attachmentType) // set the base to the implicitly provided value, and remove this implicit argument from the list diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 871b4af6ba..044d5055a1 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17779,16 +17779,8 @@ func attachmentReferenceAuthorization( attachmentType *sema.CompositeType, baseAccess sema.Access, ) (Authorization, error) { - // Map the entitlements of the accessing reference through the attachment's entitlement map to get the authorization of this reference - var attachmentReferenceAuth Authorization = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess == nil { - return attachmentReferenceAuth, nil - } - attachmentReferenceAccess, err := attachmentType.AttachmentEntitlementAccess.Image(baseAccess, func() ast.Range { return ast.EmptyRange }) - if err != nil { - return nil, err - } - return ConvertSemaAccessToStaticAuthorization(interpreter, attachmentReferenceAccess), nil + // The attachment reference has the same entitlements as the base access + return ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), nil } func attachmentBaseAuthorization( @@ -17805,12 +17797,7 @@ func attachmentBaseAndSelfValues( ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { base = v.getBaseValue() - attachmentType := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) - var attachmentReferenceAuth Authorization = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess != nil { - attachmentReferenceAuth = ConvertSemaAccessToStaticAuthorization(interpreter, attachmentType.AttachmentEntitlementAccess.Codomain()) - } // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) @@ -17885,8 +17872,8 @@ func (v *CompositeValue) GetTypeKey( ) Value { var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) - if isAttachmentType && attachmentTyp.AttachmentEntitlementAccess != nil { - access = attachmentTyp.AttachmentEntitlementAccess.Domain() + if isAttachmentType { + access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) } return v.getTypeKey(interpreter, locationRange, ty, access) } diff --git a/runtime/sema/access.go b/runtime/sema/access.go index b936f4495d..6855fa4a9b 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -72,6 +72,16 @@ func NewEntitlementSetAccess( } } +func NewEntitlementSetAccessFromSet( + set *EntitlementOrderedSet, + setKind EntitlementSetKind, +) EntitlementSetAccess { + return EntitlementSetAccess{ + Entitlements: set, + SetKind: setKind, + } +} + func (EntitlementSetAccess) isAccess() {} func (e EntitlementSetAccess) ID() TypeID { diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 8cf4b210f9..b18756240f 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -68,66 +68,37 @@ func (checker *Checker) checkAttachmentBaseType(attachmentType *CompositeType, a func (checker *Checker) checkAttachmentMembersAccess(attachmentType *CompositeType) { - // all the access modifiers for attachment members must be elements of the - // codomain of the attachment's entitlement map. This is because the codomain - // of the attachment's declared map specifies all the entitlements one can possibly - // have to that attachment, since the only way to obtain an attachment reference - // is to access it off of a base (and hence through the map). - // --------------------------------------------------- - // entitlement map M { - // E -> F - // X -> Y - // U -> V - // } - // - // access(M) attachment A for R { - // access(F) fun foo() {} - // access(Y | F) fun bar() {} - // access(V & Y) fun baz() {} - // - // access(V | Q) fun qux() {} - // } - // --------------------------------------------------- - // - // in this example, the only entitlements one can ever obtain to an &A reference are - // `F`, `Y` and `V`, and as such these are the only entitlements that may be used - // in `A`'s definition. Thus the definitions of `foo`, `bar`, and `baz` are valid, - // while the definition of `qux` is not. - var attachmentAccess Access = UnauthorizedAccess - if attachmentType.AttachmentEntitlementAccess != nil { - attachmentAccess = attachmentType.AttachmentEntitlementAccess - } - - if attachmentAccess, ok := attachmentAccess.(*EntitlementMapAccess); ok { - codomain := attachmentAccess.Codomain() - attachmentType.Members.Foreach(func(_ string, member *Member) { - if memberAccess, ok := member.Access.(EntitlementSetAccess); ok { - memberAccess.Entitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { - if !codomain.Entitlements.Contains(entitlement) { - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - AttachmentAccessModifier: attachmentAccess, - InvalidEntitlement: entitlement, - Pos: member.Identifier.Pos, - }) - } - }) - } - }) - return + // all the access modifiers for attachment members must be valid entitlements for the base type + var supportedBaseEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + baseType := attachmentType.GetBaseType() + switch base := attachmentType.GetBaseType().(type) { + case *CompositeType: + supportedBaseEntitlements = base.SupportedEntitlements() } - // if the attachment's access is public, its members may not have entitlement access attachmentType.Members.Foreach(func(_ string, member *Member) { - if _, ok := member.Access.(PrimitiveAccess); ok { - return - } - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - AttachmentAccessModifier: attachmentAccess, - Pos: member.Identifier.Pos, + var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + switch memberAccess := member.Access.(type) { + case EntitlementSetAccess: + requestedEntitlements = memberAccess.Entitlements + // if the attachment field/function is declared with mapped access, the domain of the map must be a + // subset of the supported entitlements on the base. This is because the attachment reference + // will never be able to possess any entitlements other than these, so any map relations that map + // from other entitlements will be unreachable + case *EntitlementMapAccess: + requestedEntitlements = memberAccess.Domain().Entitlements + } + + requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { + if !supportedBaseEntitlements.Contains(entitlement) { + checker.report(&InvalidAttachmentEntitlementError{ + Attachment: attachmentType, + BaseType: baseType, + InvalidEntitlement: entitlement, + Pos: member.Identifier.Pos, + }) + } }) - }) } @@ -630,14 +601,7 @@ func (checker *Checker) declareNestedDeclarations( func (checker *Checker) declareAttachmentType(declaration *ast.AttachmentDeclaration) *CompositeType { composite := checker.declareCompositeType(declaration) - composite.baseType = checker.convertNominalType(declaration.BaseType) - - attachmentAccess := checker.accessFromAstAccess(declaration.Access) - if attachmentAccess, ok := attachmentAccess.(*EntitlementMapAccess); ok { - composite.AttachmentEntitlementAccess = attachmentAccess - } - return composite } @@ -2321,12 +2285,9 @@ func (checker *Checker) declareSelfValue(selfType Type, selfDocString string) { // inside of an attachment, self is a reference to the attachment's type, because // attachments are never first class values, they must always exist inside references if typedSelfType, ok := selfType.(*CompositeType); ok && typedSelfType.Kind == common.CompositeKindAttachment { - // the `self` value in an attachment is considered fully-entitled to that attachment, or - // equivalently the entire codomain of the attachment's map + // the `self` value in an attachment is entitled to the same entitlements required by the containing function var selfAccess Access = UnauthorizedAccess - if typedSelfType.AttachmentEntitlementAccess != nil { - selfAccess = typedSelfType.AttachmentEntitlementAccess.Codomain() - } + // EntitlementsTODO: self access should be the based on the function selfType = NewReferenceType(checker.memoryGauge, selfAccess, typedSelfType) } checker.declareLowerScopedValue(selfType, selfDocString, SelfIdentifier, common.DeclarationKindSelf) @@ -2338,17 +2299,9 @@ func (checker *Checker) declareBaseValue(baseType Type, attachmentType *Composit // to be referenced by `base` baseType = NewIntersectionType(checker.memoryGauge, []*InterfaceType{typedBaseType}) } - // the `base` value in an attachment function has the set of entitlements defined by the required entitlements specified in the attachment's declaration - // ------------------------------- - // entitlement E - // entitlement F - // access(all) attachment A for R { - // require entitlement E - // access(all) fun foo() { ... } - // } - // ------------------------------- - // within the body of `foo`, the `base` value will be entitled to `E` but not `F`, because only `E` was required in the attachment's declaration + // the `base` value in an attachment is entitled to the same entitlements required by the containing function var baseAccess Access = UnauthorizedAccess + // EntitlementsTODO: base access should be the based on the function base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 363ee94f78..f1216e5814 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1900,12 +1900,7 @@ func (checker *Checker) checkEntitlementMapAccess( containerKind *common.CompositeKind, startPos ast.Position, ) { - // attachments may be declared with an entitlement map access - if declarationKind == common.DeclarationKindAttachment { - return - } - - // otherwise, mapped entitlements may only be used in structs, resources and attachments + // mapped entitlements may only be used in structs, resources and attachments if containerKind == nil || (*containerKind != common.CompositeKindResource && *containerKind != common.CompositeKindStructure && @@ -1918,7 +1913,7 @@ func (checker *Checker) checkEntitlementMapAccess( return } - // mapped entitlement fields must be, one of: + // mapped entitlement fields must be one of: // 1) An [optional] reference that is authorized to the same mapped entitlement. // 2) A function that return an [optional] reference authorized to the same mapped entitlement. // 3) A container - So if the parent is a reference, entitlements can be granted to the resulting field reference. diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index a0257cf331..421a5971b1 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4542,10 +4542,10 @@ func (e *AttachmentsNotEnabledError) Error() string { // InvalidAttachmentEntitlementError type InvalidAttachmentEntitlementError struct { - Attachment *CompositeType - AttachmentAccessModifier Access - InvalidEntitlement *EntitlementType - Pos ast.Position + Attachment *CompositeType + BaseType Type + InvalidEntitlement *EntitlementType + Pos ast.Position } var _ SemanticError = &InvalidAttachmentEntitlementError{} @@ -4567,15 +4567,10 @@ func (e *InvalidAttachmentEntitlementError) Error() string { } func (e *InvalidAttachmentEntitlementError) SecondaryError() string { - switch access := e.AttachmentAccessModifier.(type) { - case PrimitiveAccess: - return "attachments declared with `access(all)` access do not support entitlements on their members" - case *EntitlementMapAccess: - return fmt.Sprintf("`%s` must appear in the output of the entitlement mapping `%s`", - e.InvalidEntitlement.QualifiedIdentifier(), - access.Type.QualifiedIdentifier()) - } - return "" + return fmt.Sprintf("`%s` must appear in the base type `%s`", + e.InvalidEntitlement.QualifiedIdentifier(), + e.BaseType.String(), + ) } func (e *InvalidAttachmentEntitlementError) StartPosition() ast.Position { diff --git a/runtime/sema/type.go b/runtime/sema/type.go index ea9e39e33c..bbc9cd5e6d 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4292,9 +4292,8 @@ type CompositeType struct { // in a language with support for algebraic data types, // we would implement this as an argument to the CompositeKind type constructor. // Alas, this is Go, so for now these fields are only non-nil when Kind is CompositeKindAttachment - baseType Type - baseTypeDocString string - AttachmentEntitlementAccess *EntitlementMapAccess + baseType Type + baseTypeDocString string DefaultDestroyEvent *CompositeType @@ -4667,10 +4666,9 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. var access Access = UnauthorizedAccess switch attachment := indexingType.(type) { case *CompositeType: - attachmentEntitlementAccess := attachment.AttachmentEntitlementAccess - if attachmentEntitlementAccess != nil { - access = attachmentEntitlementAccess.Codomain() - } + // when accessed on an owned value, the produced attachment reference is entitled to all the + // entitlements it supports + access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ @@ -6139,15 +6137,11 @@ func (t *ReferenceType) TypeIndexingElementType(indexingType Type, astRange func } var access Access = UnauthorizedAccess - switch attachment := indexingType.(type) { + switch indexingType.(type) { case *CompositeType: - if attachment.AttachmentEntitlementAccess != nil { - var err error - access, err = attachment.AttachmentEntitlementAccess.Image(t.Authorization, astRange) - if err != nil { - return nil, err - } - } + // attachment access on a composite reference yields a reference to the attachment entitled to the same + // entitlements as that reference + access = t.Authorization } return &OptionalType{ @@ -7212,9 +7206,9 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a var access Access = UnauthorizedAccess switch attachment := indexingType.(type) { case *CompositeType: - if attachment.AttachmentEntitlementAccess != nil { - access = attachment.AttachmentEntitlementAccess.Codomain() - } + // when accessed on an owned value, the produced attachment reference is entitled to all the + // entitlements it supports + access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 43be13a3a0..34c10f887e 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -3583,7 +3583,7 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { t.Parallel() - t.Run("mapping allowed", func(t *testing.T) { + t.Run("mapping not allowed", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -3592,7 +3592,9 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { access(mapping E) attachment A for AnyStruct {} `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.InvalidMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement set not allowed", func(t *testing.T) { @@ -3611,7 +3613,7 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { require.IsType(t, &sema.InvalidEntitlementAccessError{}, errs[0]) }) - t.Run("mapping allowed in contract", func(t *testing.T) { + t.Run("mapping not allowed in contract", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -3624,12 +3626,14 @@ func TestCheckAttachmentEntitlementAccessAnnotation(t *testing.T) { X -> Y } access(mapping E) attachment A for AnyStruct { - access(Y) fun foo() {} + access(all) fun foo() {} } } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.InvalidMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement set not allowed in contract", func(t *testing.T) { @@ -4969,7 +4973,7 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { X -> Z } struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y, Z) fun foo() {} } let s = attach A() to S() From e90625643334c544d9c1d977ba3edd87b51008ee Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 15:47:39 -0400 Subject: [PATCH 26/52] fix checker tests --- runtime/sema/check_composite_declaration.go | 83 ++-- runtime/sema/check_interface_declaration.go | 4 +- runtime/sema/check_transaction_declaration.go | 2 +- runtime/sema/type.go | 13 + runtime/tests/checker/attachments_test.go | 23 +- runtime/tests/checker/entitlements_test.go | 445 ++++++++++-------- 6 files changed, 323 insertions(+), 247 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index b18756240f..4d56ff18a5 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -66,41 +66,56 @@ func (checker *Checker) checkAttachmentBaseType(attachmentType *CompositeType, a }) } +func (checker *Checker) checkAttachmentMemberAccess( + attachmentType *CompositeType, + member *Member, + baseType Type, + supportedBaseEntitlements *EntitlementOrderedSet, +) { + var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} + switch memberAccess := member.Access.(type) { + case EntitlementSetAccess: + requestedEntitlements = memberAccess.Entitlements + // if the attachment field/function is declared with mapped access, the domain of the map must be a + // subset of the supported entitlements on the base. This is because the attachment reference + // will never be able to possess any entitlements other than these, so any map relations that map + // from other entitlements will be unreachable + case *EntitlementMapAccess: + requestedEntitlements = memberAccess.Domain().Entitlements + } + + requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { + if !supportedBaseEntitlements.Contains(entitlement) { + checker.report(&InvalidAttachmentEntitlementError{ + Attachment: attachmentType, + BaseType: baseType, + InvalidEntitlement: entitlement, + Pos: member.Identifier.Pos, + }) + } + }) +} + func (checker *Checker) checkAttachmentMembersAccess(attachmentType *CompositeType) { // all the access modifiers for attachment members must be valid entitlements for the base type var supportedBaseEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} baseType := attachmentType.GetBaseType() switch base := attachmentType.GetBaseType().(type) { - case *CompositeType: + case EntitlementSupportingType: supportedBaseEntitlements = base.SupportedEntitlements() } - attachmentType.Members.Foreach(func(_ string, member *Member) { - var requestedEntitlements *EntitlementOrderedSet = &orderedmap.OrderedMap[*EntitlementType, struct{}]{} - switch memberAccess := member.Access.(type) { - case EntitlementSetAccess: - requestedEntitlements = memberAccess.Entitlements - // if the attachment field/function is declared with mapped access, the domain of the map must be a - // subset of the supported entitlements on the base. This is because the attachment reference - // will never be able to possess any entitlements other than these, so any map relations that map - // from other entitlements will be unreachable - case *EntitlementMapAccess: - requestedEntitlements = memberAccess.Domain().Entitlements - } - - requestedEntitlements.Foreach(func(entitlement *EntitlementType, _ struct{}) { - if !supportedBaseEntitlements.Contains(entitlement) { - checker.report(&InvalidAttachmentEntitlementError{ - Attachment: attachmentType, - BaseType: baseType, - InvalidEntitlement: entitlement, - Pos: member.Identifier.Pos, - }) - } + attachmentType.EffectiveInterfaceConformanceSet().ForEach(func(intf *InterfaceType) { + intf.Members.Foreach(func(_ string, member *Member) { + checker.checkAttachmentMemberAccess(attachmentType, member, baseType, supportedBaseEntitlements) }) }) + attachmentType.Members.Foreach(func(_ string, member *Member) { + checker.checkAttachmentMemberAccess(attachmentType, member, baseType, supportedBaseEntitlements) + }) + } func (checker *Checker) VisitAttachmentDeclaration(declaration *ast.AttachmentDeclaration) (_ struct{}) { @@ -117,11 +132,11 @@ func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDe checker.visitCompositeLikeDeclaration(declaration) attachmentType := checker.Elaboration.CompositeDeclarationType(declaration) - checker.checkAttachmentMembersAccess(attachmentType) checker.checkAttachmentBaseType( attachmentType, declaration.BaseType, ) + checker.checkAttachmentMembersAccess(attachmentType) return } @@ -2158,7 +2173,7 @@ func (checker *Checker) checkSpecialFunction( fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(specialFunction.FunctionDeclaration.Access), containerKind) - checker.declareSelfValue(containerType, containerDocString) + checker.declareSelfValue(fnAccess, containerType, containerDocString) if containerType.GetCompositeKind() == common.CompositeKindAttachment { // attachments cannot be interfaces, so this cast must succeed attachmentType, ok := containerType.(*CompositeType) @@ -2166,6 +2181,7 @@ func (checker *Checker) checkSpecialFunction( panic(errors.NewUnreachableError()) } checker.declareBaseValue( + fnAccess, attachmentType.baseType, attachmentType, attachmentType.baseTypeDocString) @@ -2222,9 +2238,12 @@ func (checker *Checker) checkCompositeFunctions( checker.enterValueScope() defer checker.leaveValueScope(function.EndPosition, true) - checker.declareSelfValue(selfType, selfDocString) + fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindComposite) + + checker.declareSelfValue(fnAccess, selfType, selfDocString) if selfType.GetCompositeKind() == common.CompositeKindAttachment { checker.declareBaseValue( + fnAccess, selfType.baseType, selfType, selfType.baseTypeDocString, @@ -2281,28 +2300,24 @@ func (checker *Checker) declareLowerScopedValue( } } -func (checker *Checker) declareSelfValue(selfType Type, selfDocString string) { +func (checker *Checker) declareSelfValue(fnAccess Access, selfType Type, selfDocString string) { // inside of an attachment, self is a reference to the attachment's type, because // attachments are never first class values, they must always exist inside references if typedSelfType, ok := selfType.(*CompositeType); ok && typedSelfType.Kind == common.CompositeKindAttachment { // the `self` value in an attachment is entitled to the same entitlements required by the containing function - var selfAccess Access = UnauthorizedAccess - // EntitlementsTODO: self access should be the based on the function - selfType = NewReferenceType(checker.memoryGauge, selfAccess, typedSelfType) + selfType = NewReferenceType(checker.memoryGauge, fnAccess, typedSelfType) } checker.declareLowerScopedValue(selfType, selfDocString, SelfIdentifier, common.DeclarationKindSelf) } -func (checker *Checker) declareBaseValue(baseType Type, attachmentType *CompositeType, superDocString string) { +func (checker *Checker) declareBaseValue(fnAccess Access, baseType Type, attachmentType *CompositeType, superDocString string) { if typedBaseType, ok := baseType.(*InterfaceType); ok { // we can't actually have a value of an interface type I, so instead we create a value of {I} // to be referenced by `base` baseType = NewIntersectionType(checker.memoryGauge, []*InterfaceType{typedBaseType}) } // the `base` value in an attachment is entitled to the same entitlements required by the containing function - var baseAccess Access = UnauthorizedAccess - // EntitlementsTODO: base access should be the based on the function - base := NewReferenceType(checker.memoryGauge, baseAccess, baseType) + base := NewReferenceType(checker.memoryGauge, fnAccess, baseType) checker.declareLowerScopedValue(base, superDocString, BaseIdentifier, common.DeclarationKindBase) } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index f41e718e78..be115d8660 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -218,7 +218,9 @@ func (checker *Checker) checkInterfaceFunctions( checker.enterValueScope() defer checker.leaveValueScope(function.EndPosition, false) - checker.declareSelfValue(selfType, selfDocString) + fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindInterface) + + checker.declareSelfValue(fnAccess, selfType, selfDocString) mustExit := false checkResourceLoss := false diff --git a/runtime/sema/check_transaction_declaration.go b/runtime/sema/check_transaction_declaration.go index f6be79dabe..345300cdcd 100644 --- a/runtime/sema/check_transaction_declaration.go +++ b/runtime/sema/check_transaction_declaration.go @@ -57,7 +57,7 @@ func (checker *Checker) VisitTransactionDeclaration(declaration *ast.Transaction checker.enterValueScope() defer checker.leaveValueScope(declaration.EndPosition, true) - checker.declareSelfValue(transactionType, "") + checker.declareSelfValue(UnauthorizedAccess, transactionType, "") if declaration.ParameterList != nil { checker.checkTransactionParameters(declaration, transactionType.Parameters) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index bbc9cd5e6d..8ee7a41010 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4508,6 +4508,13 @@ func (t *CompositeType) SupportedEntitlements() (set *EntitlementOrderedSet) { set.SetAll(it.SupportedEntitlements()) }) + // attachments support at least the entitlements supported by their base + if entitlementSupportingBase, isEntitlementSupportingBase := + // must ensure there is no recursive case + t.GetBaseType().(EntitlementSupportingType); isEntitlementSupportingBase && entitlementSupportingBase != t { + set.SetAll(entitlementSupportingBase.SupportedEntitlements()) + } + t.supportedEntitlements = set return set } @@ -6016,6 +6023,9 @@ func (t *ReferenceType) String() string { if t.Authorization != UnauthorizedAccess { authorization = t.Authorization.String() } + if _, isMapping := t.Authorization.(*EntitlementMapAccess); isMapping { + authorization = "mapping " + authorization + } return formatReferenceType(" ", authorization, t.Type.String()) } @@ -6027,6 +6037,9 @@ func (t *ReferenceType) QualifiedString() string { if t.Authorization != UnauthorizedAccess { authorization = t.Authorization.QualifiedString() } + if _, isMapping := t.Authorization.(*EntitlementMapAccess); isMapping { + authorization = "mapping " + authorization + } return formatReferenceType(" ", authorization, t.Type.QualifiedString()) } diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index ac40b40701..efc1c1bc3c 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -3801,13 +3801,11 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { _, err := ParseAndCheck(t, ` - access(all) resource R {} - - entitlement mapping M { - Mutate -> Insert + access(all) resource R { + access(Mutate) fun foo() {} } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(mapping Identity) let x: [String] init() { self.x = ["x"] @@ -3833,6 +3831,7 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { ` access(all) resource R { access(all) fun foo() { + // this only works because A supports all the entitlements of R self[A]!.x.append("y") } } @@ -3881,17 +3880,13 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { _, err := ParseAndCheck(t, ` - entitlement mapping M { - Mutate -> Insert - } - access(all) resource R { - access(all) fun foo() { + access(Insert) fun foo() { var xRef = self[A]!.x xRef.append("y") } } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(mapping Identity) let x: [String] init() { self.x = ["x"] @@ -4459,8 +4454,10 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { a.foo() } } - resource R {} - access(mapping M) attachment A for R { + resource R { + access(F) fun foo() {} + } + access(all) attachment A for R { access(F) fun foo() {} } access(all) fun foo(s: @R) { diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 34c10f887e..012702c884 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -969,7 +969,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { typeMismatchError.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", typeMismatchError.ActualType.QualifiedString(), ) }) @@ -1310,7 +1310,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(NM) &Int", + "auth(mapping NM) &Int", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, @@ -1397,7 +1397,7 @@ func TestCheckBasicEntitlementMappingAccess(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(NM) &Int", + "auth(mapping NM) &Int", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, @@ -2995,98 +2995,114 @@ func TestCheckEntitlementInheritance(t *testing.T) { assert.NoError(t, err) }) - t.Run("attachment default function entitlements", func(t *testing.T) { + t.Run("attachment inherited default entitled function entitlements on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G - entitlement mapping M { - E -> F - } - entitlement mapping N { - G -> E - } struct interface I { - access(mapping M) fun foo(): auth(mapping M) &Int { - return &1 as auth(mapping M) &Int + access(E) fun foo(): auth(F) &Int { + return &1 as auth(F) &Int } } - struct S {} - access(mapping N) attachment A for S: I {} + struct interface I2: I {} + struct S { + access(E) fun foo() {} + } + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() + let ref = &s as auth(E) &S + let attachmentRef: auth(E) &A = s[A]! + let i: auth(F) &Int = attachmentRef.foo() } `) assert.NoError(t, err) }) - t.Run("attachment inherited default function entitlements", func(t *testing.T) { + t.Run("attachment inherited default mapped function entitlements on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G entitlement mapping M { E -> F } - entitlement mapping N { - G -> E - } struct interface I { access(mapping M) fun foo(): auth(mapping M) &Int { return &1 as auth(mapping M) &Int } } struct interface I2: I {} - struct S {} - access(mapping N) attachment A for S: I2 {} + struct S { + access(E) fun foo() {} + } + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() + let ref = &s as auth(E) &S + let attachmentRef: auth(E) &A = s[A]! + let i: auth(F) &Int = attachmentRef.foo() } `) assert.NoError(t, err) }) - t.Run("attachment default function entitlements no attachment mapping", func(t *testing.T) { + t.Run("attachment inherited default mapped function entitlements not on base", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement E entitlement F - entitlement G entitlement mapping M { E -> F } - entitlement mapping N { - G -> E - } struct interface I { access(mapping M) fun foo(): auth(mapping M) &Int { return &1 as auth(mapping M) &Int } } + struct interface I2: I {} struct S {} - attachment A for S: I {} + access(all) attachment A for S: I2 {} fun test() { let s = attach A() to S() - let ref = &s as auth(G) &S - let i: auth(F) &Int = s[A]!.foo() // mismatch + let ref = &s as auth(E) &S + let i: auth(F) &Int = s[A]!.foo() } `) errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) + }) - // because A is declared with no mapping, all its references are unentitled - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + t.Run("attachment inherited default entitled function entitlements not on base", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement E + entitlement F + struct interface I { + access(E) fun foo(): auth(F) &Int { + return &1 as auth(F) &Int + } + } + struct interface I2: I {} + struct S {} + access(all) attachment A for S: I2 {} + fun test() { + let s = attach A() to S() + let ref = &s as auth(E) &S + let i: auth(F) &Int = s[A]!.foo() + } + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) }) } @@ -4608,11 +4624,10 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(Y) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y) fun entitled() { let a: auth(Y) &A = self let b: &S = base @@ -4633,7 +4648,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y) &A", + "&A", typeMismatchErr.ActualType.QualifiedString(), ) @@ -4654,12 +4669,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y - } struct S {} - access(mapping M) attachment A for S { - require entitlement X + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } @@ -4678,43 +4689,30 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(X) &S", + "&S", typeMismatchErr.ActualType.QualifiedString(), ) }) - t.Run("base type with no requirements", func(t *testing.T) { + t.Run("base type with sufficent requirements", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X - entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(X) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } - access(all) fun entitled() { + access(X) fun entitled() { let b: auth(X) &S = base } } `) - errs := RequireCheckerErrors(t, err, 1) - - var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) - assert.Equal(t, - typeMismatchErr.ExpectedType.QualifiedString(), - "auth(X) &S", - ) - assert.Equal(t, - "&S", - typeMismatchErr.ActualType.QualifiedString(), - ) + assert.NoError(t, err) }) t.Run("base type", func(t *testing.T) { @@ -4723,17 +4721,15 @@ func TestCheckAttachmentEntitlements(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement mapping M { - X -> Y + struct S { + access(X) fun foo() {} + access(Y) fun bar() {} } - struct S {} - access(mapping M) attachment A for S { - require entitlement X - require entitlement Y + access(all) attachment A for S { access(all) fun unentitled() { let b: &S = base } - access(all) fun entitled() { + access(X, Y) fun entitled() { let b: auth(X, Y) &S = base } } @@ -4742,46 +4738,78 @@ func TestCheckAttachmentEntitlements(t *testing.T) { assert.NoError(t, err) }) - t.Run("multiple mappings", func(t *testing.T) { + t.Run("base and self in mapped functions", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement E - entitlement F entitlement mapping M { X -> Y - E -> F } struct S { - access(E, X) fun foo() {} + access(X) fun foo() {} } - access(mapping M) attachment A for S { - access(F, Y) fun entitled() { - let a: auth(F, Y) &A = self + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let b: auth(mapping M) &S = base + let a: auth(mapping M) &A = self + + return &1 } - access(all) fun unentitled() { - let a: auth(F, Y, E) &A = self // err + } + `) + + assert.NoError(t, err) + }) + + t.Run("invalid base and self in mapped functions", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let b: auth(Y) &S = base + let a: auth(Y) &A = self + + return &1 } } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(F, Y, E) &A", + "auth(Y) &S", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y, F) &A", + "auth(mapping M) &S", + typeMismatchErr.ActualType.QualifiedString(), + ) + + require.ErrorAs(t, errs[1], &typeMismatchErr) + assert.Equal(t, + "auth(Y) &A", + typeMismatchErr.ExpectedType.QualifiedString(), + ) + assert.Equal(t, + "auth(mapping M) &A", typeMismatchErr.ActualType.QualifiedString(), ) }) - t.Run("missing in codomain", func(t *testing.T) { + t.Run("missing in S", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -4789,12 +4817,14 @@ func TestCheckAttachmentEntitlements(t *testing.T) { entitlement Y entitlement Z entitlement E - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(X) fun foo() {} + access(Y | Z) let bar: Int + init() { + self.bar = 1 + } } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(E) fun entitled() {} } `) @@ -4809,20 +4839,17 @@ func TestCheckAttachmentEntitlements(t *testing.T) { ) }) - t.Run("missing in codomain in set", func(t *testing.T) { + t.Run("missing in set", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X entitlement Y entitlement Z entitlement E - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(Y | E | Z) fun entitled() {} } `) @@ -4844,11 +4871,10 @@ func TestCheckAttachmentEntitlements(t *testing.T) { entitlement X entitlement E entitlement F - entitlement mapping M { - E -> F + struct S { + access(F) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(F, X, E) fun entitled() {} } `) @@ -4879,9 +4905,9 @@ func TestCheckAttachmentEntitlements(t *testing.T) { X -> Y } struct S { - access(Y) fun foo() {} + access(X) fun foo() {} } - access(mapping M) attachment A for S { + access(all) attachment A for S { access(mapping M) let x: auth(mapping M) &S init() { self.x = &S() as auth(Y) &S @@ -4968,11 +4994,9 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} access(all) attachment A for S { access(Y, Z) fun foo() {} } @@ -4983,6 +5007,27 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { assert.NoError(t, err) }) + t.Run("basic owned fully entitled missing X", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement Z + struct S { + access(Y, Z) fun foo() {} + } + access(all) attachment A for S { + access(Y, Z) fun foo() {} + } + let s = attach A() to S() + let a: auth(X, Y, Z) &A = s[A]! + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + t.Run("basic owned intersection fully entitled", func(t *testing.T) { t.Parallel() @@ -4990,13 +5035,13 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct interface I { + access(Y, Z) fun foo() } - struct interface I {} - struct S: I {} - access(mapping M) attachment A for I { + struct S: I { + access(Y, Z) fun foo() {} + } + access(all) attachment A for I { access(Y, Z) fun foo() {} } let s: {I} = attach A() to S() @@ -5006,33 +5051,51 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { assert.NoError(t, err) }) - t.Run("basic reference mapping", func(t *testing.T) { + t.Run("basic owned intersection fully entitled missing X", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` entitlement X entitlement Y - entitlement E - entitlement F - entitlement mapping M { - X -> Y - E -> F + entitlement Z + struct interface I { + access(Y, Z) fun foo() + } + struct S: I { + access(Y, Z) fun foo() {} + } + access(all) attachment A for I { + access(Y, Z) fun foo() {} } + let s: {I} = attach A() to S() + let a: auth(X, Y, Z) &A = s[A]! + `) + + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + + t.Run("basic reference mapping", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement X + entitlement E struct S { access(X, E) fun foo() {} } - access(mapping M) attachment A for S { - access(Y, F) fun foo() {} + access(all) attachment A for S { + access(X, E) fun foo() {} } let s = attach A() to S() - let yRef = &s as auth(X) &S - let fRef = &s as auth(E) &S + let xRef = &s as auth(X) &S + let eRef = &s as auth(E) &S let bothRef = &s as auth(X, E) &S - let a1: auth(Y) &A = yRef[A]! - let a2: auth(F) &A = fRef[A]! - let a3: auth(F) &A = yRef[A]! // err - let a4: auth(Y) &A = fRef[A]! // err - let a5: auth(Y, F) &A = bothRef[A]! + let a1: auth(X) &A = xRef[A]! + let a2: auth(E) &A = eRef[A]! + let a3: auth(E) &A = xRef[A]! // err + let a4: auth(X) &A = eRef[A]! // err + let a5: auth(X, E) &A = bothRef[A]! `) errs := RequireCheckerErrors(t, err, 2) @@ -5040,21 +5103,21 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { var typeMismatchErr *sema.TypeMismatchError require.ErrorAs(t, errs[0], &typeMismatchErr) assert.Equal(t, - "auth(F) &A?", + "auth(E) &A?", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(Y) &A?", + "auth(X) &A?", typeMismatchErr.ActualType.QualifiedString(), ) require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, - "auth(Y) &A?", + "auth(X) &A?", typeMismatchErr.ExpectedType.QualifiedString(), ) assert.Equal(t, - "auth(F) &A?", + "auth(E) &A?", typeMismatchErr.ActualType.QualifiedString(), ) }) @@ -5063,51 +5126,15 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X - entitlement Y - entitlement mapping M { - X -> Y - } - struct S {} - access(mapping M) attachment A for S { - access(Y) fun foo() {} - } - let s = attach A() to S() - let ref = &s as &S - let a1: auth(Y) &A = ref[A]! - `) - - errs := RequireCheckerErrors(t, err, 1) - - var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) - assert.Equal(t, - "auth(Y) &A?", - typeMismatchErr.ExpectedType.QualifiedString(), - ) - assert.Equal(t, - "&A?", - typeMismatchErr.ActualType.QualifiedString(), - ) - }) - - t.Run("entitled access access(all) attachment", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement X entitlement Y - entitlement mapping M { - X -> Y - } struct S { - access(X) fun foo() {} + access(Y) fun foo() {} } access(all) attachment A for S { - access(all) fun foo() {} + access(Y) fun foo() {} } let s = attach A() to S() - let ref = &s as auth(X) &S + let ref = &s as &S let a1: auth(Y) &A = ref[A]! `) @@ -5160,41 +5187,63 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { ) }) - t.Run("unrepresentable access mapping", func(t *testing.T) { + t.Run("base attachment access in mapped function", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - entitlement X - entitlement Y - entitlement Z - entitlement E - entitlement F - entitlement G + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let s: auth(mapping M) &A = base[A]! - entitlement mapping M { - X -> Y - X -> Z - E -> F - E -> G - } + return &1 + } + } + `) - struct S { - access(X, E) fun foo() {} - } + assert.NoError(t, err) + }) - access(mapping M) attachment A for S { - access(Y, Z, F, G) fun foo() {} - } + t.Run("invalid base attachment access in mapped function", func(t *testing.T) { + t.Parallel() - let s = attach A() to S() - let ref = (&s as auth(X) &S) as auth(X | E) &S - let a1 = ref[A]! + _, err := ParseAndCheck(t, ` + entitlement X + entitlement Y + entitlement mapping M { + X -> Y + } + struct S { + access(X) fun foo() {} + } + access(all) attachment A for S { + access(mapping M) fun foo(): auth(mapping M) &Int { + let s: auth(Y) &A? = base[A] + + return &1 + } + } `) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.UnrepresentableEntitlementMapOutputError{}, errs[0]) - require.IsType(t, &sema.InvalidTypeIndexingError{}, errs[1]) + var typeMismatchErr *sema.TypeMismatchError + require.ErrorAs(t, errs[0], &typeMismatchErr) + assert.Equal(t, + "auth(Y) &A?", + typeMismatchErr.ExpectedType.QualifiedString(), + ) + assert.Equal(t, + "auth(mapping M) &A?", + typeMismatchErr.ActualType.QualifiedString(), + ) }) } From 2b76f5918e5143fe92caf90511d4260e35061ab8 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:54:24 -0500 Subject: [PATCH 27/52] begin interpreter impl --- runtime/interpreter/value.go | 29 ++++++++------------- runtime/sema/check_composite_declaration.go | 9 ++++--- runtime/sema/type_test.go | 6 ++--- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 044d5055a1..17ac8fd01b 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17774,30 +17774,23 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc ) } -func attachmentReferenceAuthorization( - interpreter *Interpreter, - attachmentType *sema.CompositeType, - baseAccess sema.Access, -) (Authorization, error) { - // The attachment reference has the same entitlements as the base access - return ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), nil -} - func attachmentBaseAuthorization( interpreter *Interpreter, attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess + // EntitlementsTODO: this should not be unauthorized return auth } func attachmentBaseAndSelfValues( interpreter *Interpreter, + fnAccess sema.Access, v *CompositeValue, ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { base = v.getBaseValue() - var attachmentReferenceAuth Authorization = UnauthorizedAccess + attachmentReferenceAuth := ConvertSemaAccessToStaticAuthorization(interpreter, fnAccess) // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) @@ -17851,16 +17844,16 @@ func (v *CompositeValue) getTypeKey( return Nil } attachmentType := keyType.(*sema.CompositeType) - // dynamically set the attachment's base to this composite, but with authorization based on the requested access on that attachment + // dynamically set the attachment's base to this composite attachment.setBaseValue(interpreter, v) - // Map the entitlements of the accessing reference through the attachment's entitlement map to get the authorization of this reference - attachmentReferenceAuth, err := attachmentReferenceAuthorization(interpreter, attachmentType, baseAccess) - if err != nil { - return Nil - } - - attachmentRef := NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, attachment, attachmentType) + // The attachment reference has the same entitlements as the base access + attachmentRef := NewEphemeralReferenceValue( + interpreter, + ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess), + attachment, + attachmentType, + ) return NewSomeValueNonCopying(interpreter, attachmentRef) } diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 4d56ff18a5..356cc5a8ba 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2027,16 +2027,19 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( func (checker *Checker) checkDefaultDestroyEventParam( param Parameter, astParam *ast.Parameter, - containerType ContainerType, + containerType EntitlementSupportingType, containerDeclaration ast.Declaration, ) { paramType := param.TypeAnnotation.Type paramDefaultArgument := astParam.DefaultArgument + access := NewEntitlementSetAccessFromSet(containerType.SupportedEntitlements(), Conjunction) + // make `self` and `base` available when checking default arguments so the fields of the composite are available - checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) + checker.declareSelfValue(access, containerType, containerDeclaration.DeclarationDocString()) if compositeContainer, isComposite := containerType.(*CompositeType); isComposite && compositeContainer.Kind == common.CompositeKindAttachment { checker.declareBaseValue( + access, compositeContainer.baseType, compositeContainer, compositeContainer.baseTypeDocString) @@ -2063,7 +2066,7 @@ func (checker *Checker) checkDefaultDestroyEventParam( func (checker *Checker) checkDefaultDestroyEvent( eventType *CompositeType, eventDeclaration ast.CompositeLikeDeclaration, - containerType ContainerType, + containerType EntitlementSupportingType, containerDeclaration ast.Declaration, ) { diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index a81854a54f..ac157de230 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2162,7 +2162,7 @@ func TestReferenceType_String(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", referenceType.String(), ) }) @@ -2216,7 +2216,7 @@ func TestReferenceType_QualifiedString(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(M) &Int", + "auth(mapping M) &Int", referenceType.QualifiedString(), ) }) @@ -2252,7 +2252,7 @@ func TestReferenceType_QualifiedString(t *testing.T) { referenceType := NewReferenceType(nil, access, IntType) assert.Equal(t, - "auth(C.M) &Int", + "auth(mapping C.M) &Int", referenceType.QualifiedString(), ) }) From 1f9ce66f1e7a9cc1db6e7eb15a039fb520bbf8f5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:55:34 -0500 Subject: [PATCH 28/52] base has different auth in each function --- runtime/interpreter/interpreter.go | 3 +-- runtime/interpreter/interpreter_expression.go | 9 +++++++- runtime/interpreter/value.go | 21 +++++++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 0f13c34235..24e2be266b 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1348,9 +1348,8 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( var auth Authorization = UnauthorizedAccess attachmentType := interpreter.MustSemaTypeOfValue(value).(*sema.CompositeType) - // Self's type in the constructor is codomain of the attachment's entitlement map, since + // Self's type in the constructor is fully entitled, since // the constructor can only be called when in possession of the base resource - // if the attachment is declared with access(all) access, then self is unauthorized auth = ConvertSemaAccessToStaticAuthorization( interpreter, diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 7575e9b1e9..5cf26bb490 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1404,7 +1404,12 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta // set it on the attachment's `CompositeValue` yet, because the value does not exist. // Instead, we create an implicit constructor argument containing a reference to the base. - var auth Authorization = UnauthorizedAccess + // within the constructor, the attachment's base and self references should be fully entitled, + // as the constructor of the attachment is only callable by the owner of the base + baseType := interpreter.MustSemaTypeOfValue(base).(sema.EntitlementSupportingType) + baseAccess := sema.NewEntitlementSetAccessFromSet(baseType.SupportedEntitlements(), sema.Conjunction) + auth := ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) + attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) var baseValue Value = NewEphemeralReferenceValue( @@ -1432,6 +1437,8 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta nil, ).(*CompositeValue) + attachment.setBaseValue(interpreter, base) + // we enforce this in the checker if !ok { panic(errors.NewUnreachableError()) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 17ac8fd01b..071050f866 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16323,7 +16323,7 @@ type CompositeValue struct { // 2) When a resource `r`'s destructor is invoked, all of `r`'s attachments' destructors will also run, and // have their `base` fields set to `&r` // 3) When a value is transferred, this field is copied between its attachments - base *EphemeralReferenceValue + base *CompositeValue QualifiedIdentifier string Kind common.CompositeKind isDestroyed bool @@ -17175,7 +17175,7 @@ func (v *CompositeValue) ConformsToStaticType( } if compositeType.Kind == common.CompositeKindAttachment { - base := v.getBaseValue().Value + base := v.getBaseValue(interpreter, UnauthorizedAccess).Value if base == nil || !base.ConformsToStaticType(interpreter, locationRange, results) { return false } @@ -17686,11 +17686,7 @@ func NewEnumCaseValue( return v } -func (v *CompositeValue) getBaseValue() *EphemeralReferenceValue { - return v.base -} - -func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { +func (v *CompositeValue) getBaseValue(interpreter *Interpreter, fnAuth Authorization) *EphemeralReferenceValue { attachmentType, ok := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) if !ok { panic(errors.NewUnreachableError()) @@ -17704,8 +17700,11 @@ func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeV baseType = ty } - authorization := attachmentBaseAuthorization(interpreter, v) - v.base = NewEphemeralReferenceValue(interpreter, authorization, base, baseType) + return NewEphemeralReferenceValue(interpreter, fnAuth, v.base, baseType) +} + +func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { + v.base = base } func attachmentMemberName(ty sema.Type) string { @@ -17776,6 +17775,7 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc func attachmentBaseAuthorization( interpreter *Interpreter, + fnAccess sema.Access, attachment *CompositeValue, ) Authorization { var auth Authorization = UnauthorizedAccess @@ -17788,10 +17788,9 @@ func attachmentBaseAndSelfValues( fnAccess sema.Access, v *CompositeValue, ) (base *EphemeralReferenceValue, self *EphemeralReferenceValue) { - base = v.getBaseValue() - attachmentReferenceAuth := ConvertSemaAccessToStaticAuthorization(interpreter, fnAccess) + base = v.getBaseValue(interpreter, attachmentReferenceAuth) // in attachment functions, self is a reference value self = NewEphemeralReferenceValue(interpreter, attachmentReferenceAuth, v, interpreter.MustSemaTypeOfValue(v)) From 6bcad92725f575c625eb2d292b11ef4672c97841 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:55:50 -0500 Subject: [PATCH 29/52] function types carry access information --- runtime/convertValues_test.go | 1 + runtime/interpreter/interpreter.go | 1 + runtime/interpreter/value_function_test.go | 2 + runtime/interpreter/value_test.go | 1 + runtime/sema/check_composite_declaration.go | 3 ++ runtime/sema/checker.go | 2 + runtime/sema/crypto_algorithm_types.go | 2 + runtime/sema/meta_type.go | 1 + runtime/sema/runtime_type_constructors.go | 11 ++++ runtime/sema/string_type.go | 11 ++++ runtime/sema/type.go | 52 ++++++++++++++++++- runtime/sema/type_test.go | 2 + runtime/stdlib/account.go | 2 + runtime/stdlib/block.go | 2 + runtime/stdlib/log.go | 1 + runtime/stdlib/panic.go | 1 + runtime/stdlib/publickey.go | 1 + runtime/stdlib/random.go | 1 + runtime/tests/checker/interface_test.go | 1 + runtime/tests/interpreter/interface_test.go | 3 ++ runtime/tests/interpreter/interpreter_test.go | 1 + runtime/tests/interpreter/runtimetype_test.go | 3 ++ 22 files changed, 104 insertions(+), 1 deletion(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index f80e41330c..74a4721e58 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -131,6 +131,7 @@ func TestRuntimeExportValue(t *testing.T) { testFunction := &interpreter.InterpretedFunctionValue{ Type: sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.VoidTypeAnnotation, ), diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 24e2be266b..09977ba3df 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3566,6 +3566,7 @@ func functionTypeFunction(invocation Invocation) Value { interpreter, sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, parameterTypes, sema.NewTypeAnnotation(returnType), ), diff --git a/runtime/interpreter/value_function_test.go b/runtime/interpreter/value_function_test.go index ffcfdfddd3..c40143298e 100644 --- a/runtime/interpreter/value_function_test.go +++ b/runtime/interpreter/value_function_test.go @@ -45,6 +45,7 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) @@ -71,6 +72,7 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index cf79dfed79..962a3d5221 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -3619,6 +3619,7 @@ func TestValue_ConformsToStaticType(t *testing.T) { functionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { TypeAnnotation: sema.IntTypeAnnotation, diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 356cc5a8ba..e2b894b55a 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1292,11 +1292,13 @@ func (checker *Checker) checkCompositeLikeConformance( initializerType := NewSimpleFunctionType( compositeType.ConstructorPurity, + UnauthorizedAccess, compositeType.ConstructorParameters, VoidTypeAnnotation, ) interfaceInitializerType := NewSimpleFunctionType( conformance.InitializerPurity, + UnauthorizedAccess, conformance.InitializerParameters, VoidTypeAnnotation, ) @@ -2192,6 +2194,7 @@ func (checker *Checker) checkSpecialFunction( functionType := NewSimpleFunctionType( purity, + fnAccess, parameters, VoidTypeAnnotation, ) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index f1216e5814..61a9a8eff2 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -132,6 +132,7 @@ var _ ast.ExpressionVisitor[Type] = &Checker{} var baseFunctionType = NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, VoidTypeAnnotation, ) @@ -1128,6 +1129,7 @@ func (checker *Checker) convertFunctionType(t *ast.FunctionType) Type { return NewSimpleFunctionType( purity, + UnauthorizedAccess, parameters, returnTypeAnnotation, ) diff --git a/runtime/sema/crypto_algorithm_types.go b/runtime/sema/crypto_algorithm_types.go index b74d326bff..a084b634d5 100644 --- a/runtime/sema/crypto_algorithm_types.go +++ b/runtime/sema/crypto_algorithm_types.go @@ -110,6 +110,7 @@ const HashAlgorithmTypeHashFunctionName = "hash" var HashAlgorithmTypeHashFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -128,6 +129,7 @@ const HashAlgorithmTypeHashWithTagFunctionName = "hashWithTag" var HashAlgorithmTypeHashWithTagFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/meta_type.go b/runtime/sema/meta_type.go index 6ee455c1ab..689f170eef 100644 --- a/runtime/sema/meta_type.go +++ b/runtime/sema/meta_type.go @@ -50,6 +50,7 @@ var MetaTypeAnnotation = NewTypeAnnotation(MetaType) var MetaTypeIsSubtypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: "of", diff --git a/runtime/sema/runtime_type_constructors.go b/runtime/sema/runtime_type_constructors.go index 86bec84979..f23779af26 100644 --- a/runtime/sema/runtime_type_constructors.go +++ b/runtime/sema/runtime_type_constructors.go @@ -26,6 +26,7 @@ type RuntimeTypeConstructor struct { var MetaTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -34,6 +35,7 @@ const OptionalTypeFunctionName = "OptionalType" var OptionalTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -48,6 +50,7 @@ const VariableSizedArrayTypeFunctionName = "VariableSizedArrayType" var VariableSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -62,6 +65,7 @@ const ConstantSizedArrayTypeFunctionName = "ConstantSizedArrayType" var ConstantSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "type", @@ -83,6 +87,7 @@ const DictionaryTypeFunctionName = "DictionaryType" var DictionaryTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -100,6 +105,7 @@ const CompositeTypeFunctionName = "CompositeType" var CompositeTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -114,6 +120,7 @@ const InterfaceTypeFunctionName = "InterfaceType" var InterfaceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -128,6 +135,7 @@ const FunctionTypeFunctionName = "FunctionType" var FunctionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "parameters", @@ -149,6 +157,7 @@ const IntersectionTypeFunctionName = "IntersectionType" var IntersectionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "types", @@ -166,6 +175,7 @@ const ReferenceTypeFunctionName = "ReferenceType" var ReferenceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "entitlements", @@ -187,6 +197,7 @@ const CapabilityTypeFunctionName = "CapabilityType" var CapabilityTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/string_type.go b/runtime/sema/string_type.go index bcb562c6d6..0d7ff70407 100644 --- a/runtime/sema/string_type.go +++ b/runtime/sema/string_type.go @@ -128,6 +128,7 @@ func init() { var StringTypeConcatFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -146,6 +147,7 @@ Returns a new string which contains the given string concatenated to the end of var StringTypeSliceFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -192,6 +194,7 @@ var ByteArrayArrayTypeAnnotation = NewTypeAnnotation(ByteArrayArrayType) var StringTypeDecodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -219,6 +222,7 @@ The byte array of the UTF-8 encoding var StringTypeToLowerFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -245,6 +249,7 @@ var StringFunctionType = func() *FunctionType { functionType := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -302,6 +307,7 @@ var StringFunctionType = func() *FunctionType { var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -314,6 +320,7 @@ var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -330,6 +337,7 @@ var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -344,6 +352,7 @@ var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( var StringTypeJoinFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -362,6 +371,7 @@ var StringTypeJoinFunctionType = NewSimpleFunctionType( var StringTypeSplitFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "separator", @@ -377,6 +387,7 @@ var StringTypeSplitFunctionType = NewSimpleFunctionType( var StringTypeReplaceAllFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "of", diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 8ee7a41010..10811a9127 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -406,6 +406,7 @@ const IsInstanceFunctionName = "isInstance" var IsInstanceFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -426,6 +427,7 @@ const GetTypeFunctionName = "getType" var GetTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -440,6 +442,7 @@ const ToStringFunctionName = "toString" var ToStringFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -477,6 +480,7 @@ func FromStringFunctionDocstring(ty Type) string { func FromStringFunctionType(ty Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -506,6 +510,7 @@ func FromBigEndianBytesFunctionDocstring(ty Type) string { func FromBigEndianBytesFunctionType(ty Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -527,6 +532,7 @@ const ToBigEndianBytesFunctionName = "toBigEndianBytes" var ToBigEndianBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -809,6 +815,7 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { return &FunctionType{ Purity: functionPurity, + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -819,6 +826,7 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { TypeAnnotation: NewTypeAnnotation( &FunctionType{ Purity: functionPurity, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -1025,6 +1033,7 @@ var SaturatingArithmeticTypeFunctionTypes = map[Type]*FunctionType{} func registerSaturatingArithmeticType(t Type) { SaturatingArithmeticTypeFunctionTypes[t] = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2372,6 +2381,7 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2380,6 +2390,7 @@ func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2388,6 +2399,7 @@ func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { func ArrayRemoveFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2401,6 +2413,7 @@ func ArrayRemoveFunctionType(elementType Type) *FunctionType { func ArrayInsertFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2420,6 +2433,7 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { typeAnnotation := NewTypeAnnotation(arrayType) return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2434,6 +2448,7 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "of", @@ -2448,6 +2463,7 @@ func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { func ArrayContainsFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2462,6 +2478,7 @@ func ArrayContainsFunctionType(elementType Type) *FunctionType { func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2476,6 +2493,7 @@ func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { func ArrayAppendFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2490,6 +2508,7 @@ func ArrayAppendFunctionType(elementType Type) *FunctionType { func ArraySliceFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -2509,6 +2528,7 @@ func ArraySliceFunctionType(elementType Type) *FunctionType { func ArrayReverseFunctionType(arrayType ArrayType) *FunctionType { return &FunctionType{ Parameters: []Parameter{}, + Access: UnauthorizedAccess, ReturnTypeAnnotation: NewTypeAnnotation(arrayType), Purity: FunctionPurityView, } @@ -2518,6 +2538,7 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * // fun filter(_ function: ((T): Bool)): [T] // funcType: elementType -> Bool funcType := &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2529,6 +2550,7 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * } return &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2567,6 +2589,7 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * // transformFuncType: elementType -> U transformFuncType := &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2577,6 +2600,7 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * } return &FunctionType{ + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -3154,6 +3178,7 @@ func (p FunctionPurity) String() string { type FunctionType struct { Purity FunctionPurity + Access Access ReturnTypeAnnotation TypeAnnotation Arity *Arity ArgumentExpressionsCheck ArgumentExpressionsCheck @@ -3167,11 +3192,13 @@ type FunctionType struct { func NewSimpleFunctionType( purity FunctionPurity, + access Access, parameters []Parameter, returnTypeAnnotation TypeAnnotation, ) *FunctionType { return &FunctionType{ Purity: purity, + Access: access, Parameters: parameters, ReturnTypeAnnotation: returnTypeAnnotation, } @@ -3525,6 +3552,7 @@ func (t *FunctionType) RewriteWithIntersectionTypes() (Type, bool) { return &FunctionType{ Purity: t.Purity, + Access: t.Access, TypeParameters: rewrittenTypeParameters, Parameters: rewrittenParameters, ReturnTypeAnnotation: NewTypeAnnotation(rewrittenReturnType), @@ -3642,6 +3670,7 @@ func (t *FunctionType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type return &FunctionType{ Purity: t.Purity, + Access: t.Access, Parameters: newParameters, ReturnTypeAnnotation: NewTypeAnnotation(newReturnType), Arity: t.Arity, @@ -3697,7 +3726,7 @@ func (t *FunctionType) Map(gauge common.MemoryGauge, typeParamMap map[*TypeParam returnType := t.ReturnTypeAnnotation.Map(gauge, typeParamMap, f) - functionType := NewSimpleFunctionType(t.Purity, newParameters, returnType) + functionType := NewSimpleFunctionType(t.Purity, t.Access, newParameters, returnType) functionType.TypeParameters = newTypeParameters return f(functionType) } @@ -4021,6 +4050,7 @@ func init() { func NumberConversionFunctionType(numberType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4055,6 +4085,7 @@ func baseFunctionVariable(name string, ty *FunctionType, docString string) *Vari var AddressConversionFunctionType = &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4085,6 +4116,7 @@ Returns an Address from the given byte array var AddressTypeFromBytesFunctionType = &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4190,6 +4222,7 @@ func numberFunctionArgumentExpressionsChecker(targetType Type) ArgumentExpressio func pathConversionFunctionType(pathType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "identifier", @@ -4226,6 +4259,7 @@ func init() { typeName, &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{{Name: "T"}}, ReturnTypeAnnotation: MetaTypeAnnotation, }, @@ -4713,6 +4747,7 @@ func CompositeForEachAttachmentFunctionType(t common.CompositeKind) *FunctionTyp Identifier: "f", TypeAnnotation: NewTypeAnnotation( &FunctionType{ + Access: UnauthorizedAccess, Parameters: []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -4815,6 +4850,7 @@ func (t *CompositeType) SetNestedType(name string, nestedType ContainedType) { func (t *CompositeType) ConstructorFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, + Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: NewTypeAnnotation(t), @@ -4824,6 +4860,7 @@ func (t *CompositeType) ConstructorFunctionType() *FunctionType { func (t *CompositeType) InitializerFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, + Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -5824,6 +5861,7 @@ func (t *DictionaryType) initializeMemberResolvers() { func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -5838,6 +5876,7 @@ func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5860,6 +5899,7 @@ func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { func DictionaryRemoveFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5880,6 +5920,7 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun(K): Bool funcType := NewSimpleFunctionType( functionPurity, + UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5892,6 +5933,7 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun forEachKey(_ function: fun(K): Bool): Void return NewSimpleFunctionType( functionPurity, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -6332,6 +6374,7 @@ const AddressTypeToBytesFunctionName = `toBytes` var AddressTypeToBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -6820,6 +6863,7 @@ var _ Type = &TransactionType{} func (t *TransactionType) EntryPointFunctionType() *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, + UnauthorizedAccess, append(t.Parameters, t.PrepareParameters...), VoidTypeAnnotation, ) @@ -6828,6 +6872,7 @@ func (t *TransactionType) EntryPointFunctionType() *FunctionType { func (t *TransactionType) PrepareFunctionType() *FunctionType { return &FunctionType{ Purity: FunctionPurityImpure, + Access: UnauthorizedAccess, IsConstructor: true, Parameters: t.PrepareParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -6836,6 +6881,7 @@ func (t *TransactionType) PrepareFunctionType() *FunctionType { var transactionTypeExecuteFunctionType = &FunctionType{ Purity: FunctionPurityImpure, + Access: UnauthorizedAccess, IsConstructor: true, ReturnTypeAnnotation: VoidTypeAnnotation, } @@ -7461,6 +7507,7 @@ func CapabilityTypeBorrowFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: NewTypeAnnotation( &OptionalType{ @@ -7482,6 +7529,7 @@ func CapabilityTypeCheckFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, + Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: BoolTypeAnnotation, } @@ -7712,6 +7760,7 @@ var PublicKeyArrayTypeAnnotation = NewTypeAnnotation(PublicKeyArrayType) var PublicKeyVerifyFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Identifier: "signature", @@ -7735,6 +7784,7 @@ var PublicKeyVerifyFunctionType = NewSimpleFunctionType( var PublicKeyVerifyPoPFunctionType = NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index ac157de230..d3fed2e804 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -1988,6 +1988,7 @@ func TestMapType(t *testing.T) { } original := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -2015,6 +2016,7 @@ func TestMapType(t *testing.T) { } mapped := NewSimpleFunctionType( FunctionPurityView, + UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index a30d3784b1..f53c2c07b7 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -43,6 +43,7 @@ Creates a new account, paid by the given existing account // auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account var accountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: "payer", @@ -2039,6 +2040,7 @@ Returns the account for the given address var getAccountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/block.go b/runtime/stdlib/block.go index 9edaf3a626..457e80d956 100644 --- a/runtime/stdlib/block.go +++ b/runtime/stdlib/block.go @@ -34,6 +34,7 @@ Returns the current block, i.e. the block which contains the currently executed var getCurrentBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, nil, sema.BlockTypeAnnotation, ) @@ -44,6 +45,7 @@ Returns the block at the given height. If the given block does not exist the fun var getBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: "at", diff --git a/runtime/stdlib/log.go b/runtime/stdlib/log.go index 792f8273df..389ae0a3c2 100644 --- a/runtime/stdlib/log.go +++ b/runtime/stdlib/log.go @@ -26,6 +26,7 @@ import ( var LogFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/panic.go b/runtime/stdlib/panic.go index d9f6296780..703dbe61a2 100644 --- a/runtime/stdlib/panic.go +++ b/runtime/stdlib/panic.go @@ -45,6 +45,7 @@ Terminates the program unconditionally and reports a message which explains why var panicFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/publickey.go b/runtime/stdlib/publickey.go index 069fa27989..c26768b628 100644 --- a/runtime/stdlib/publickey.go +++ b/runtime/stdlib/publickey.go @@ -32,6 +32,7 @@ Constructs a new public key var publicKeyConstructorFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: sema.PublicKeyTypePublicKeyFieldName, diff --git a/runtime/stdlib/random.go b/runtime/stdlib/random.go index 7340db4559..20a621457d 100644 --- a/runtime/stdlib/random.go +++ b/runtime/stdlib/random.go @@ -36,6 +36,7 @@ Follow best practices to prevent security issues when using this function var unsafeRandomFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, + sema.UnauthorizedAccess, nil, sema.UInt64TypeAnnotation, ) diff --git a/runtime/tests/checker/interface_test.go b/runtime/tests/checker/interface_test.go index 0eae0fcd4e..5acae0ba8f 100644 --- a/runtime/tests/checker/interface_test.go +++ b/runtime/tests/checker/interface_test.go @@ -1772,6 +1772,7 @@ func TestCheckInvalidInterfaceUseAsTypeSuggestion(t *testing.T) { assert.Equal(t, &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.NewTypeAnnotation( diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index a66b429611..937bc7edde 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -563,6 +563,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -675,6 +676,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -787,6 +789,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, + sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 31a1e05804..b8780d769a 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -9893,6 +9893,7 @@ func TestInterpretHostFunctionStaticType(t *testing.T) { nil, &sema.FunctionType{ Purity: sema.FunctionPurityView, + Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.MetaTypeAnnotation, }, ), diff --git a/runtime/tests/interpreter/runtimetype_test.go b/runtime/tests/interpreter/runtimetype_test.go index d3411a8880..f2320f72b8 100644 --- a/runtime/tests/interpreter/runtimetype_test.go +++ b/runtime/tests/interpreter/runtimetype_test.go @@ -410,6 +410,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.StringTypeAnnotation, @@ -426,6 +427,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ {TypeAnnotation: sema.StringTypeAnnotation}, {TypeAnnotation: sema.IntTypeAnnotation}, @@ -441,6 +443,7 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ + Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.StringTypeAnnotation, }, }, From 804c8a1484be28ab631f114c02de367c39dad15b Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:58:05 -0400 Subject: [PATCH 30/52] use function access for base --- runtime/interpreter/value.go | 13 ++-------- .../tests/interpreter/entitlements_test.go | 24 +++++++------------ 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 071050f866..e96a65f5ec 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16802,7 +16802,8 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var base *EphemeralReferenceValue var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { - base, self = attachmentBaseAndSelfValues(interpreter, v) + functionAccess := function.FunctionType().Access + base, self = attachmentBaseAndSelfValues(interpreter, functionAccess, v) } return NewBoundFunctionValue(interpreter, function, &self, base, nil) } @@ -17773,16 +17774,6 @@ func (v *CompositeValue) forEachAttachmentFunction(interpreter *Interpreter, loc ) } -func attachmentBaseAuthorization( - interpreter *Interpreter, - fnAccess sema.Access, - attachment *CompositeValue, -) Authorization { - var auth Authorization = UnauthorizedAccess - // EntitlementsTODO: this should not be unauthorized - return auth -} - func attachmentBaseAndSelfValues( interpreter *Interpreter, fnAccess sema.Access, diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index 09e1740a01..6b60ba2732 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -2166,15 +2166,12 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y, Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S {} + access(all) attachment A for S {} fun test(): auth(Y, Z) &A { let s = attach A() to S() return s[A]! @@ -2200,20 +2197,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(Y | Z) fun entitled(): auth(Y, Z) &A { + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &A { return self } } - fun test(): auth(Y, Z) &A { + fun test(): auth(Y | Z) &A { let s = attach A() to S() return s[A]!.entitled() } @@ -2228,7 +2222,7 @@ func TestInterpretEntitledAttachments(t *testing.T) { nil, func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, 2, - sema.Conjunction, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) From 9369f0be038ace1f32b14bc8d8ce54683bbff82c Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 16:58:27 -0400 Subject: [PATCH 31/52] add missing access --- runtime/sema/checker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 61a9a8eff2..619a12087e 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1354,6 +1354,7 @@ func (checker *Checker) functionType( return &FunctionType{ Purity: PurityFromAnnotation(purity), + Access: access, TypeParameters: convertedTypeParameters, Parameters: convertedParameters, ReturnTypeAnnotation: convertedReturnTypeAnnotation, From 34cdf7e3c8c09646897ddd0fef8c3dab43336f27 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:56:07 -0500 Subject: [PATCH 32/52] update interpreter tests --- runtime/interpreter/statictype.go | 3 +- runtime/tests/interpreter/attachments_test.go | 21 +- .../tests/interpreter/entitlements_test.go | 330 ++++++++---------- 3 files changed, 152 insertions(+), 202 deletions(-) diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index 975ecbd29d..ee727f7802 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -25,6 +25,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" @@ -931,7 +932,7 @@ func ConvertSemaAccessToStaticAuthorization( ) Authorization { switch access := access.(type) { case sema.PrimitiveAccess: - if access.Equal(sema.UnauthorizedAccess) { + if access.Equal(sema.UnauthorizedAccess) || access.Equal(sema.PrimitiveAccess(ast.AccessNotSpecified)) { return UnauthorizedAccess } diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 678634da7e..2564512574 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -2046,32 +2046,23 @@ func TestInterpretForEachAttachment(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement E entitlement F - entitlement X entitlement Y - entitlement mapping M { - E -> F - } - entitlement mapping N { - X -> Y - } - entitlement mapping O { - E -> Y + struct S { + access(F, Y) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(F) fun foo(_ x: Int): Int { return 7 + x } } - access(mapping N) attachment B for S { + access(all) attachment B for S { access(Y) fun foo(): Int { return 10 } } - access(mapping O) attachment C for S { + access(all) attachment C for S { access(Y) fun foo(_ x: Int): Int { return 8 + x } } fun test(): Int { var s = attach C() to attach B() to attach A() to S() - let ref = &s as auth(E) &S + let ref = &s as auth(F, Y) &S var i = 0 ref.forEachAttachment(fun(attachmentRef: &AnyStructAttachment) { if let a = attachmentRef as? auth(F) &A { diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index 6b60ba2732..4c0833e2c7 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -2232,20 +2232,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(Y | Z) fun entitled(): &S { + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &S { return base } } - fun test(): &S { + fun test(): auth(Y | Z) &S { let s = attach A() to S() return s[A]!.entitled() } @@ -2256,32 +2253,70 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.True( t, - interpreter.UnauthorizedAccess.Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic call return authorized base", func(t *testing.T) { + t.Run("basic call unbound method", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X entitlement Y entitlement Z - entitlement mapping M { - X -> Y - X -> Z + struct S { + access(Y | Z) fun foo() {} + } + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &A { + return self + } } - struct S {} - access(mapping M) attachment A for S { - require entitlement X - access(Y | Z) fun entitled(): auth(X) &S { + fun test(): auth(Y | Z) &A { + let s = attach A() to S() + let foo = s[A]!.entitled + return foo() + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.True( + t, + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + ) + }) + + t.Run("basic call unbound method base", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement Y + entitlement Z + struct S { + access(Y | Z) fun foo() {} + } + access(all) attachment A for S { + access(Y | Z) fun entitled(): auth(Y | Z) &S { return base } } - fun test(): auth(X) &S { - let s = attach A() to S() with (X) - return s[A]!.entitled() + fun test(): auth(Y | Z) &S { + let s = attach A() to S() + let foo = s[A]!.entitled + return foo() } `) @@ -2292,9 +2327,9 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.X"} }, - 1, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.Y", "S.test.Z"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) @@ -2305,21 +2340,13 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S {} - fun test(): auth(F, G) &A { + access(all) attachment A for S {} + fun test(): auth(E) &A { let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]! @@ -2333,8 +2360,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2346,25 +2373,17 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(F | Z) fun entitled(): auth(Y, Z, F, G) &A { + access(all) attachment A for S { + access(E | G) fun entitled(): auth(E | G) &A { return self } } - fun test(): auth(Y, Z, F, G) &A { + fun test(): auth(E | G) &A { let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]!.entitled() @@ -2378,40 +2397,32 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G", "S.test.Y", "S.test.Z"} }, - 4, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.E", "S.test.G"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic ref call return base", func(t *testing.T) { + t.Run("basic ref call conjunction", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - access(F | Z) fun entitled(): &S { - return base + access(all) attachment A for S { + access(E) fun entitled(): auth(E) &A { + return self } } - fun test(): &S { + fun test(): auth(E) &A { let s = attach A() to S() - let ref = &s as auth(E) &S + let ref = &s as auth(E, G) &S return ref[A]!.entitled() } `) @@ -2421,37 +2432,33 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.True( t, - interpreter.UnauthorizedAccess.Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), + interpreter.NewEntitlementSetAuthorization( + nil, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, + sema.Conjunction, + ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) - t.Run("basic ref call return entitled base", func(t *testing.T) { + t.Run("basic ref call return base", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S { + access(X, E, G) fun foo() {} } - struct S {} - access(mapping M) attachment A for S { - require entitlement E - access(F | Z) fun entitled(): auth(E) &S { + access(all) attachment A for S { + access(X | E) fun entitled(): auth(X | E) &S { return base } } - fun test(): auth(E) &S { - let s = attach A() to S() with(E) + fun test(): auth(X | E) &S { + let s = attach A() to S() let ref = &s as auth(E) &S return ref[A]!.entitled() } @@ -2464,9 +2471,9 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.E"} }, - 1, - sema.Conjunction, + func() []common.TypeID { return []common.TypeID{"S.test.E", "S.test.X"} }, + 2, + sema.Disjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) }) @@ -2477,24 +2484,18 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + struct S: I { + access(X, E) fun foo() {} + } + struct interface I { + access(X, E, G) fun foo() } - struct S: I {} - struct interface I {} - access(mapping M) attachment A for I {} - fun test(): auth(F, G) &A { + access(all) attachment A for I {} + fun test(): auth(G) &A { let s = attach A() to S() - let ref = &s as auth(E) &{I} + let ref = &s as auth(G) &{I} return ref[A]! } `) @@ -2506,8 +2507,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.G"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2521,21 +2522,13 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R {} - fun test(): auth(F, G) &A { + access(all) attachment A for R {} + fun test(): auth(E) &A { let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) let ref = account.storage.borrow(from: /storage/foo)! @@ -2552,8 +2545,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G"} }, - 2, + func() []common.TypeID { return []common.TypeID{"S.test.E"} }, + 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2567,28 +2560,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - access(F | Z) fun entitled(): auth(F, G, Y, Z) &A { + access(all) attachment A for R { + access(X, E) fun entitled(): auth(X, E) &A { return self } } - fun test(): auth(F, G, Y, Z) &A { + fun test(): auth(X, E) &A { let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) - let ref = account.storage.borrow(from: /storage/foo)! + let ref = account.storage.borrow(from: /storage/foo)! return ref[A]!.entitled() } `, sema.Config{ @@ -2602,8 +2587,8 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.F", "S.test.G", "S.test.Y", "S.test.Z"} }, - 4, + func() []common.TypeID { return []common.TypeID{"S.test.X", "S.test.E"} }, + 2, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), ) @@ -2617,29 +2602,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter, _ := testAccount(t, address, true, nil, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - require entitlement X - access(F | Z) fun entitled(): auth(X) &R { + access(all) attachment A for R { + access(X) fun entitled(): auth(X) &R { return base } } fun test(): auth(X) &R { - let r <- attach A() to <-create R() with (X) + let r <- attach A() to <-create R() account.storage.save(<-r, to: /storage/foo) - let ref = account.storage.borrow(from: /storage/foo)! + let ref = account.storage.borrow(from: /storage/foo)! return ref[A]!.entitled() } `, sema.Config{ @@ -2666,29 +2642,19 @@ func TestInterpretEntitledAttachments(t *testing.T) { inter := parseCheckAndInterpret(t, ` entitlement X - entitlement Y - entitlement Z entitlement E - entitlement F entitlement G - entitlement mapping M { - X -> Y - X -> Z - E -> F - X -> F - E -> G + resource R { + access(X, E, G) fun foo() {} } - resource R {} - access(mapping M) attachment A for R { - require entitlement E - require entitlement X + access(all) attachment A for R { init() { - let x = self as! auth(Y, Z, F, G) &A - let y = base as! auth(X, E) &R + let x = self as! auth(X, E, G) &A + let y = base as! auth(X, E, G) &R } } fun test() { - let r <- attach A() to <-create R() with (E, X) + let r <- attach A() to <-create R() destroy r } `) @@ -2697,28 +2663,20 @@ func TestInterpretEntitledAttachments(t *testing.T) { require.NoError(t, err) }) - t.Run("composed mapped attachment access", func(t *testing.T) { + t.Run("composed attachment access", func(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - entitlement X - entitlement Y entitlement Z - entitlement E - entitlement F - entitlement G - entitlement mapping M { - X -> Y - E -> F + entitlement Y + struct S { + access(Y) fun foo() {} } - entitlement mapping N { - Z -> X - G -> F + struct T { + access(Z) fun foo() {} } - struct S {} - struct T {} - access(mapping M) attachment A for S { + access(all) attachment A for S { access(self) let t: T init(t: T) { self.t = t @@ -2727,10 +2685,10 @@ func TestInterpretEntitledAttachments(t *testing.T) { return &self.t as auth(Z) &T } } - access(mapping N) attachment B for T {} - fun test(): auth(X) &B { + access(all) attachment B for T {} + fun test(): auth(Z) &B { let s = attach A(t: attach B() to T()) to S() - let ref = &s as auth(X) &S + let ref = &s as auth(Y) &S return ref[A]!.getT()[B]! } `) @@ -2742,7 +2700,7 @@ func TestInterpretEntitledAttachments(t *testing.T) { t, interpreter.NewEntitlementSetAuthorization( nil, - func() []common.TypeID { return []common.TypeID{"S.test.X"} }, + func() []common.TypeID { return []common.TypeID{"S.test.Z"} }, 1, sema.Conjunction, ).Equal(value.(*interpreter.EphemeralReferenceValue).Authorization), From f2e169b1abf50233a53544d7b39782de9a0a954e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:38:24 -0400 Subject: [PATCH 33/52] fix test --- runtime/tests/checker/function_test.go | 1 + runtime/tests/checker/member_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/runtime/tests/checker/function_test.go b/runtime/tests/checker/function_test.go index 5e1facb8cb..bfff43f279 100644 --- a/runtime/tests/checker/function_test.go +++ b/runtime/tests/checker/function_test.go @@ -537,6 +537,7 @@ func TestCheckNativeFunctionDeclaration(t *testing.T) { assert.Equal(t, sema.NewTypeAnnotation(&sema.FunctionType{ + Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{ { Identifier: "foo", diff --git a/runtime/tests/checker/member_test.go b/runtime/tests/checker/member_test.go index ea2b592220..0114187214 100644 --- a/runtime/tests/checker/member_test.go +++ b/runtime/tests/checker/member_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -274,6 +275,7 @@ func TestCheckFunctionTypeReceiverType(t *testing.T) { assert.Equal(t, &sema.FunctionType{ Purity: sema.FunctionPurityImpure, + Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{}, ReturnTypeAnnotation: sema.VoidTypeAnnotation, }, From 4f0862f8978b4f57fe2c8f1d7081e206e2acd6c1 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:46:00 -0400 Subject: [PATCH 34/52] fix tests --- runtime/entitlements_test.go | 17 ++++++------- runtime/sema/type.go | 14 +++++++++-- runtime/tests/checker/attachments_test.go | 30 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/runtime/entitlements_test.go b/runtime/entitlements_test.go index 048d179412..bafe338e25 100644 --- a/runtime/entitlements_test.go +++ b/runtime/entitlements_test.go @@ -217,7 +217,7 @@ func TestRuntimeAccountEntitlementSaveAndLoadFail(t *testing.T) { require.ErrorAs(t, err, &interpreter.ForceCastTypeMismatchError{}) } -func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { +func TestRuntimeAccountEntitlementAttachment(t *testing.T) { t.Parallel() storage := NewTestLedger(nil, nil) @@ -226,16 +226,13 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { deployTx := DeploymentTransaction("Test", []byte(` access(all) contract Test { - access(all) entitlement X access(all) entitlement Y - access(all) entitlement mapping M { - X -> Y - } - - access(all) resource R {} + access(all) resource R { + access(Y) fun foo() {} + } - access(mapping M) attachment A for R { + access(all) attachment A for R { access(Y) fun foo() {} } @@ -252,7 +249,7 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { prepare(signer: auth(Storage, Capabilities) &Account) { let r <- Test.createRWithA() signer.storage.save(<-r, to: /storage/foo) - let cap = signer.capabilities.storage.issue(/storage/foo) + let cap = signer.capabilities.storage.issue(/storage/foo) signer.capabilities.publish(cap, at: /public/foo) } } @@ -263,7 +260,7 @@ func TestRuntimeAccountEntitlementAttachmentMap(t *testing.T) { transaction { prepare(signer: &Account) { - let ref = signer.capabilities.borrow(/public/foo)! + let ref = signer.capabilities.borrow(/public/foo)! ref[Test.A]!.foo() } } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 10811a9127..74a7cfcaa7 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4709,7 +4709,12 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) + supportedEntitlements := attachment.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = UnauthorizedAccess + } else { + access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) + } } return &OptionalType{ @@ -7267,7 +7272,12 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - access = NewEntitlementSetAccessFromSet(attachment.SupportedEntitlements(), Conjunction) + supportedEntitlements := attachment.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = UnauthorizedAccess + } else { + access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) + } } return &OptionalType{ diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index efc1c1bc3c..196f85936f 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -4661,3 +4661,33 @@ func TestCheckAttachmentPurity(t *testing.T) { assert.IsType(t, &sema.PurityError{}, errs[0]) }) } + +func TestCheckAccessOnNonEntitlementSupportingBaseCreatesUnauthorizedReference(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + access(all) contract Test { + access(all) resource R {} + access(all) attachment A for R {} + access(all) fun makeRWithA(): @R { + return <- attach A() to <-create R() + } + } + access(all) fun main(): &Test.A? { + let r <- Test.makeRWithA() + var a = r[Test.A] + + a = returnSameRef(a) + + destroy r + return a + } + + access(all) fun returnSameRef(_ ref: &Test.A?): &Test.A? { + return ref + } + `) + + require.NoError(t, err) +} From 6143dafea61cd72d90286c914a5ae34f68812fe9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 17:47:51 -0400 Subject: [PATCH 35/52] dont create empty entitlement sets in the interpreter --- runtime/interpreter/value.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index e96a65f5ec..18fdd621fc 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17856,7 +17856,12 @@ func (v *CompositeValue) GetTypeKey( var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) if isAttachmentType { - access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) + supportedEntitlements := attachmentTyp.SupportedEntitlements() + if supportedEntitlements.Len() == 0 { + access = sema.UnauthorizedAccess + } else { + access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) + } } return v.getTypeKey(interpreter, locationRange, ty, access) } From 79e7c80b48fe988c367273d8e507bb9f28924e01 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:56:23 -0500 Subject: [PATCH 36/52] dont create empty entitlement sets anywhere --- runtime/interpreter/interpreter.go | 2 +- runtime/interpreter/interpreter_expression.go | 2 +- runtime/interpreter/value.go | 7 +------ runtime/sema/access.go | 8 ++++++-- runtime/sema/type.go | 14 ++------------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 09977ba3df..b1711c4c5c 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1353,7 +1353,7 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( auth = ConvertSemaAccessToStaticAuthorization( interpreter, - sema.NewEntitlementSetAccessFromSet(attachmentType.SupportedEntitlements(), sema.Conjunction), + sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction), ) self = NewEphemeralReferenceValue(interpreter, auth, value, attachmentType) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 5cf26bb490..76e5b4e036 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1407,7 +1407,7 @@ func (interpreter *Interpreter) VisitAttachExpression(attachExpression *ast.Atta // within the constructor, the attachment's base and self references should be fully entitled, // as the constructor of the attachment is only callable by the owner of the base baseType := interpreter.MustSemaTypeOfValue(base).(sema.EntitlementSupportingType) - baseAccess := sema.NewEntitlementSetAccessFromSet(baseType.SupportedEntitlements(), sema.Conjunction) + baseAccess := sema.NewAccessFromEntitlementSet(baseType.SupportedEntitlements(), sema.Conjunction) auth := ConvertSemaAccessToStaticAuthorization(interpreter, baseAccess) attachmentType := interpreter.Program.Elaboration.AttachTypes(attachExpression) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 18fdd621fc..a06647f1a5 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17856,12 +17856,7 @@ func (v *CompositeValue) GetTypeKey( var access sema.Access = sema.UnauthorizedAccess attachmentTyp, isAttachmentType := ty.(*sema.CompositeType) if isAttachmentType { - supportedEntitlements := attachmentTyp.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = sema.UnauthorizedAccess - } else { - access = sema.NewEntitlementSetAccessFromSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) - } + access = sema.NewAccessFromEntitlementSet(attachmentTyp.SupportedEntitlements(), sema.Conjunction) } return v.getTypeKey(interpreter, locationRange, ty, access) } diff --git a/runtime/sema/access.go b/runtime/sema/access.go index 6855fa4a9b..e0f49b7f37 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -72,10 +72,14 @@ func NewEntitlementSetAccess( } } -func NewEntitlementSetAccessFromSet( +func NewAccessFromEntitlementSet( set *EntitlementOrderedSet, setKind EntitlementSetKind, -) EntitlementSetAccess { +) Access { + if set.Len() == 0 { + return UnauthorizedAccess + } + return EntitlementSetAccess{ Entitlements: set, SetKind: setKind, diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 74a7cfcaa7..ae495f103d 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4709,12 +4709,7 @@ func (t *CompositeType) TypeIndexingElementType(indexingType Type, _ func() ast. case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - supportedEntitlements := attachment.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = UnauthorizedAccess - } else { - access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) - } + access = NewAccessFromEntitlementSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ @@ -7272,12 +7267,7 @@ func (t *IntersectionType) TypeIndexingElementType(indexingType Type, _ func() a case *CompositeType: // when accessed on an owned value, the produced attachment reference is entitled to all the // entitlements it supports - supportedEntitlements := attachment.SupportedEntitlements() - if supportedEntitlements.Len() == 0 { - access = UnauthorizedAccess - } else { - access = NewEntitlementSetAccessFromSet(supportedEntitlements, Conjunction) - } + access = NewAccessFromEntitlementSet(attachment.SupportedEntitlements(), Conjunction) } return &OptionalType{ From a0ba35962aca9099929f6dbf30972fcfe2a7ca1d Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 11:07:02 -0400 Subject: [PATCH 37/52] add tests for mapped self and base types --- runtime/interpreter/interpreter_expression.go | 7 +- runtime/sema/access.go | 10 + runtime/sema/check_composite_declaration.go | 3 +- runtime/sema/check_member_expression.go | 4 +- runtime/sema/type.go | 1 + runtime/tests/checker/attachments_test.go | 303 +++++++++++++++++- runtime/tests/interpreter/attachments_test.go | 102 ++++++ 7 files changed, 423 insertions(+), 7 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 76e5b4e036..20a18c0f2a 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1180,8 +1180,11 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx switch expression.Operation { case ast.OperationFailableCast, ast.OperationForceCast: - valueStaticType := value.StaticType(interpreter) - valueSemaType := interpreter.MustConvertStaticToSemaType(valueStaticType) + // if the value itself has a mapped entitlement type in its authorization + // (e.g. if it is a reference to `self` or `base` in an attachment function with mapped access) + // substitution must also be performed on its entitlements + valueSemaType := interpreter.substituteMappedEntitlements(interpreter.MustSemaTypeOfValue(value)) + valueStaticType := ConvertSemaToStaticType(interpreter, valueSemaType) isSubType := interpreter.IsSubTypeOfSemaType(valueStaticType, expectedType) switch expression.Operation { diff --git a/runtime/sema/access.go b/runtime/sema/access.go index e0f49b7f37..1e77e71755 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -32,6 +32,7 @@ import ( type Access interface { isAccess() + isPrimitiveAccess() bool ID() TypeID String() string QualifiedString() string @@ -87,6 +88,9 @@ func NewAccessFromEntitlementSet( } func (EntitlementSetAccess) isAccess() {} +func (EntitlementSetAccess) isPrimitiveAccess() bool { + return false +} func (e EntitlementSetAccess) ID() TypeID { entitlementTypeIDs := make([]TypeID, 0, e.Entitlements.Len()) @@ -279,6 +283,9 @@ func NewEntitlementMapAccess(mapType *EntitlementMapType) *EntitlementMapAccess } func (*EntitlementMapAccess) isAccess() {} +func (*EntitlementMapAccess) isPrimitiveAccess() bool { + return false +} func (e *EntitlementMapAccess) ID() TypeID { return e.Type.ID() @@ -448,6 +455,9 @@ type PrimitiveAccess ast.PrimitiveAccess var _ Access = PrimitiveAccess(0) func (PrimitiveAccess) isAccess() {} +func (PrimitiveAccess) isPrimitiveAccess() bool { + return true +} func (PrimitiveAccess) ID() TypeID { panic(errors.NewUnreachableError()) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index e2b894b55a..bbcf977e30 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2176,7 +2176,8 @@ func (checker *Checker) checkSpecialFunction( checker.enterValueScope() defer checker.leaveValueScope(specialFunction.EndPosition, checkResourceLoss) - fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(specialFunction.FunctionDeclaration.Access), containerKind) + // initializers and destructors are considered fully entitled to their container type + fnAccess := NewAccessFromEntitlementSet(containerType.SupportedEntitlements(), Conjunction) checker.declareSelfValue(fnAccess, containerType, containerDocString) if containerType.GetCompositeKind() == common.CompositeKindAttachment { diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index e2afe56770..b5fe0b52c8 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -376,7 +376,9 @@ func (checker *Checker) visitMember(expression *ast.MemberExpression, isAssignme // in the current location of the checker, along with the authorzation with which the result can be used func (checker *Checker) isReadableMember(accessedType Type, member *Member, resultingType Type, accessRange func() ast.Range) (bool, Access) { if checker.Config.AccessCheckMode.IsReadableAccess(member.Access) || - checker.containerTypes[member.ContainerType] { + // only allow references unrestricted access to members in their own container that are not entitled + // this prevents rights escalation attacks on entitlements + (member.Access.isPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { if mappedAccess, isMappedAccess := member.Access.(*EntitlementMapAccess); isMappedAccess { return checker.mapAccess(mappedAccess, accessedType, resultingType, accessRange) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index ae495f103d..27be861d80 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -304,6 +304,7 @@ func TypeActivationNestedType(typeActivation *VariableActivation, qualifiedIdent // CompositeKindedType is a type which has a composite kind type CompositeKindedType interface { Type + EntitlementSupportingType GetCompositeKind() common.CompositeKind } diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 196f85936f..540bfb6e08 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -1330,6 +1330,29 @@ func TestCheckAttachmentSelfTyping(t *testing.T) { require.NoError(t, err) }) + + t.Run("self access restricted", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + resource R { + access(E) fun foo() {} + } + attachment Test for R { + access(E) fun bar() {} + fun foo(t: &Test) { + t.bar() + } + }`, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) } func TestCheckAttachmentType(t *testing.T) { @@ -4025,6 +4048,283 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { require.NoError(t, err) }) + + t.Run("entitlement mapped field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement G + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(G) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) let x: [String] + init() { + self.x = ["x"] + } + } + fun foo() { + let r <- attach A() to <- create R() + let a = r[A]! + let x: auth(F) &[String] = a.x + destroy r + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function self value cast", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(F) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function self value cast invalid access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(E) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function base value cast", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(F) let x: Int + init() { + self.x = 3 + } + access(E) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("entitlement mapped function base value cast invalid access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) let x: Int + init() { + self.x = 3 + } + access(F) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function self value access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(E) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + return &self.x + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("entitlement mapped function base value access", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) let x: Int + init() { + self.x = 3 + } + access(F) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + return &base.x + } + } + fun foo(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) } func TestCheckAttachmentsResourceReference(t *testing.T) { @@ -4446,9 +4746,6 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { ` entitlement F entitlement E - entitlement mapping M { - E -> F - } fun bar (attachmentRef: &AnyResourceAttachment) { if let a = attachmentRef as? auth(F) &A { a.foo() diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 2564512574..4837ba2a1a 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1949,6 +1949,108 @@ func TestInterpretAttachmentDefensiveCheck(t *testing.T) { }) } +func TestInterpretAttachmentMappedMembers(t *testing.T) { + + t.Parallel() + + t.Run("mapped self cast", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement E + entitlement F + entitlement G + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(E) fun foo() {} + access(F) fun bar() {} + } + access(all) attachment A for R { + access(F) let x: Int + init() { + self.x = 3 + } + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteSelf = self as? auth(F) &A { + return &concreteSelf.x + } + return &1 + } + } + fun test(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + + t.Run("mapped base cast", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement E + entitlement F + entitlement mapping M { + E -> F + } + + access(all) resource R { + access(F) let x: Int + init() { + self.x = 3 + } + access(E) fun bar() {} + } + access(all) attachment A for R { + access(mapping M) fun foo(): auth(mapping M) &Int { + if let concreteBase = base as? auth(F) &R { + return &concreteBase.x + } + return &1 + } + } + fun test(): &Int { + let r <- attach A() to <- create R() + let a = r[A]! + let i = a.foo() + destroy r + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + +} + func TestInterpretForEachAttachment(t *testing.T) { t.Parallel() From a679e4f24efd620fe9ceaa44b5cd2a1fa5cc4cd5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 13:10:27 -0400 Subject: [PATCH 38/52] fix lint --- runtime/interpreter/interpreter.go | 3 +-- runtime/tests/checker/entitlements_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index b1711c4c5c..fa18479c02 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1346,12 +1346,11 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( var self MemberAccessibleValue = value if declaration.Kind() == common.CompositeKindAttachment { - var auth Authorization = UnauthorizedAccess attachmentType := interpreter.MustSemaTypeOfValue(value).(*sema.CompositeType) // Self's type in the constructor is fully entitled, since // the constructor can only be called when in possession of the base resource - auth = ConvertSemaAccessToStaticAuthorization( + auth := ConvertSemaAccessToStaticAuthorization( interpreter, sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction), ) diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 012702c884..0c51f50297 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4694,7 +4694,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { ) }) - t.Run("base type with sufficent requirements", func(t *testing.T) { + t.Run("base type with sufficient requirements", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` From 7c18e16945f25bd7d0dd4e222c076790f5e6edbb Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Nov 2023 13:40:11 -0400 Subject: [PATCH 39/52] fix self/contract/account access entitlement functions --- runtime/interpreter/interpreter_expression.go | 10 +++++ runtime/interpreter/statictype.go | 3 +- runtime/interpreter/value.go | 18 +++++++++ runtime/sema/access.go | 8 ++-- runtime/sema/check_member_expression.go | 2 +- runtime/tests/interpreter/attachments_test.go | 39 +++++++++++++++++++ 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 20a18c0f2a..8a16607921 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -335,6 +335,16 @@ func (interpreter *Interpreter) checkMemberAccess( targetStaticType := target.StaticType(interpreter) + // if both the targetType and the expectedType are references, we unwrap them and instead compare their underlying type + // this is because the "real" type of the target's entitlements may not yet be populated until after the member access + // succeeds, leading to extraneous errors here. The entitlements are enforced instead at checking time. + if referenceStaticType, isReferenceStaticType := targetStaticType.(*ReferenceStaticType); isReferenceStaticType { + targetStaticType = referenceStaticType.ReferencedType + if referenceType, isReferenceType := expectedType.(*sema.ReferenceType); isReferenceType { + expectedType = referenceType.Type + } + } + if !interpreter.IsSubTypeOfSemaType(targetStaticType, expectedType) { targetSemaType := interpreter.MustConvertStaticToSemaType(targetStaticType) diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index ee727f7802..975ecbd29d 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -25,7 +25,6 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/onflow/atree" - "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" @@ -932,7 +931,7 @@ func ConvertSemaAccessToStaticAuthorization( ) Authorization { switch access := access.(type) { case sema.PrimitiveAccess: - if access.Equal(sema.UnauthorizedAccess) || access.Equal(sema.PrimitiveAccess(ast.AccessNotSpecified)) { + if access.Equal(sema.UnauthorizedAccess) { return UnauthorizedAccess } diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index a06647f1a5..618b20533c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16803,6 +16803,24 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { functionAccess := function.FunctionType().Access + // with respect to entitlements, any access inside an attachment that is not an entitlement access + // does not provide any entitlements to base and self + // E.g. consider: + // + // access(E) fun foo() {} + // access(self) fun bar() { + // self.foo() + // } + // access(all) fun baz() { + // self.bar() + // } + // + // clearly `bar` should be callable within `baz`, but we cannot allow `foo` + // to be callable within `bar`, or it will be possible to access `E` entitled + // methods on `base` + if functionAccess.IsPrimitiveAccess() { + functionAccess = sema.UnauthorizedAccess + } base, self = attachmentBaseAndSelfValues(interpreter, functionAccess, v) } return NewBoundFunctionValue(interpreter, function, &self, base, nil) diff --git a/runtime/sema/access.go b/runtime/sema/access.go index 1e77e71755..ae6d976ad4 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -32,7 +32,7 @@ import ( type Access interface { isAccess() - isPrimitiveAccess() bool + IsPrimitiveAccess() bool ID() TypeID String() string QualifiedString() string @@ -88,7 +88,7 @@ func NewAccessFromEntitlementSet( } func (EntitlementSetAccess) isAccess() {} -func (EntitlementSetAccess) isPrimitiveAccess() bool { +func (EntitlementSetAccess) IsPrimitiveAccess() bool { return false } @@ -283,7 +283,7 @@ func NewEntitlementMapAccess(mapType *EntitlementMapType) *EntitlementMapAccess } func (*EntitlementMapAccess) isAccess() {} -func (*EntitlementMapAccess) isPrimitiveAccess() bool { +func (*EntitlementMapAccess) IsPrimitiveAccess() bool { return false } @@ -455,7 +455,7 @@ type PrimitiveAccess ast.PrimitiveAccess var _ Access = PrimitiveAccess(0) func (PrimitiveAccess) isAccess() {} -func (PrimitiveAccess) isPrimitiveAccess() bool { +func (PrimitiveAccess) IsPrimitiveAccess() bool { return true } diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index b5fe0b52c8..a9fe2e837e 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -378,7 +378,7 @@ func (checker *Checker) isReadableMember(accessedType Type, member *Member, resu if checker.Config.AccessCheckMode.IsReadableAccess(member.Access) || // only allow references unrestricted access to members in their own container that are not entitled // this prevents rights escalation attacks on entitlements - (member.Access.isPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { + (member.Access.IsPrimitiveAccess() && checker.containerTypes[member.ContainerType]) { if mappedAccess, isMappedAccess := member.Access.(*EntitlementMapAccess); isMappedAccess { return checker.mapAccess(mappedAccess, accessedType, resultingType, accessRange) diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 4837ba2a1a..f4aa344231 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1949,6 +1949,45 @@ func TestInterpretAttachmentDefensiveCheck(t *testing.T) { }) } +func TestInterpretAttachmentSelfAccessMembers(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) resource R{ + access(all) fun baz() {} + } + access(all) attachment A for R{ + access(all) fun foo() {} + access(self) fun qux1() { + self.foo() + base.baz() + } + access(contract) fun qux2() { + self.foo() + base.baz() + } + access(account) fun qux3() { + self.foo() + base.baz() + } + access(all) fun bar() { + self.qux1() + self.qux2() + self.qux3() + } + } + + access(all) fun main() { + var r <- attach A() to <- create R() + r[A]!.bar() + destroy r + } + `) + + _, err := inter.Invoke("main") + require.NoError(t, err) +} + func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() From 092b491d5220b6b4bedb7fb2f5de21f163ba0e39 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Nov 2023 14:25:48 -0400 Subject: [PATCH 40/52] better fix --- runtime/interpreter/interpreter_expression.go | 10 ---------- runtime/sema/check_composite_declaration.go | 4 ++++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 8a16607921..20a18c0f2a 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -335,16 +335,6 @@ func (interpreter *Interpreter) checkMemberAccess( targetStaticType := target.StaticType(interpreter) - // if both the targetType and the expectedType are references, we unwrap them and instead compare their underlying type - // this is because the "real" type of the target's entitlements may not yet be populated until after the member access - // succeeds, leading to extraneous errors here. The entitlements are enforced instead at checking time. - if referenceStaticType, isReferenceStaticType := targetStaticType.(*ReferenceStaticType); isReferenceStaticType { - targetStaticType = referenceStaticType.ReferencedType - if referenceType, isReferenceType := expectedType.(*sema.ReferenceType); isReferenceType { - expectedType = referenceType.Type - } - } - if !interpreter.IsSubTypeOfSemaType(targetStaticType, expectedType) { targetSemaType := interpreter.MustConvertStaticToSemaType(targetStaticType) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index bbcf977e30..b074784d86 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2246,6 +2246,10 @@ func (checker *Checker) checkCompositeFunctions( defer checker.leaveValueScope(function.EndPosition, true) fnAccess := checker.effectiveMemberAccess(checker.accessFromAstAccess(function.Access), ContainerKindComposite) + // all non-entitlement functions produce unauthorized references in attachments + if fnAccess.IsPrimitiveAccess() { + fnAccess = UnauthorizedAccess + } checker.declareSelfValue(fnAccess, selfType, selfDocString) if selfType.GetCompositeKind() == common.CompositeKindAttachment { From 03292430c4c05139090b25e27b76bed619b07aa8 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 6 Nov 2023 11:07:37 -0500 Subject: [PATCH 41/52] fix crash on use of bound functions involving base inside constructor of attachment --- runtime/interpreter/interpreter.go | 1 + runtime/tests/interpreter/attachments_test.go | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index fa18479c02..ca96ec2f94 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1360,6 +1360,7 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( // set the base to the implicitly provided value, and remove this implicit argument from the list implicitArgumentPos := len(invocation.Arguments) - 1 invocation.Base = invocation.Arguments[implicitArgumentPos].(*EphemeralReferenceValue) + value.base = invocation.Base.Value.(*CompositeValue) invocation.Arguments[implicitArgumentPos] = nil invocation.Arguments = invocation.Arguments[:implicitArgumentPos] invocation.ArgumentTypes[implicitArgumentPos] = nil diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index f4aa344231..7afb08ec3b 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -2087,7 +2087,6 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { value.(*interpreter.EphemeralReferenceValue).Value, ) }) - } func TestInterpretForEachAttachment(t *testing.T) { @@ -2225,6 +2224,49 @@ func TestInterpretForEachAttachment(t *testing.T) { AssertValuesEqual(t, inter, interpreter.NewUnmeteredIntValueFromInt64(0), value) }) + t.Run("bound function", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + entitlement F + + access(all) struct S { + access(F) let x: Int + init() { + self.x = 3 + } + } + access(all) attachment A for S { + access(F) var funcPtr: fun(): auth(F) ∬ + init() { + self.funcPtr = self.foo + } + access(F) fun foo(): auth(F) &Int { + return &base.x + } + } + fun test(): &Int { + let r = attach A() to S() + let rRef = &r as auth(F) &S + let a = rRef[A]! + let i = a.foo() + return i + } + `) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value.(*interpreter.EphemeralReferenceValue).Value, + ) + }) + t.Run("access fields", func(t *testing.T) { t.Parallel() From cc65c15c3e166f6c2ac0a3f3dccfd3d7aa0e10ca Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 6 Nov 2023 11:18:32 -0500 Subject: [PATCH 42/52] disable entitlement-mapped fields on attachments for now --- runtime/sema/checker.go | 10 +++++ runtime/sema/errors.go | 24 ++++++++++++ runtime/tests/checker/attachments_test.go | 38 +++++++++++-------- runtime/tests/checker/entitlements_test.go | 23 ++++++----- runtime/tests/interpreter/attachments_test.go | 25 ++++++++++-- 5 files changed, 92 insertions(+), 28 deletions(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 619a12087e..99f5888197 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1916,6 +1916,16 @@ func (checker *Checker) checkEntitlementMapAccess( return } + // due to potential security issues, entitlement mappings are disabled on attachments for now + if *containerKind == common.CompositeKindAttachment { + checker.report( + &InvalidAttachmentMappedEntitlementMemberError{ + Pos: startPos, + }, + ) + return + } + // mapped entitlement fields must be one of: // 1) An [optional] reference that is authorized to the same mapped entitlement. // 2) A function that return an [optional] reference authorized to the same mapped entitlement. diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 421a5971b1..6d036a2496 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4192,6 +4192,30 @@ func (e *InvalidMappedEntitlementMemberError) EndPosition(common.MemoryGauge) as return e.Pos } +// InvalidAttachmentMappedEntitlementMemberError +type InvalidAttachmentMappedEntitlementMemberError struct { + Pos ast.Position +} + +var _ SemanticError = &InvalidAttachmentMappedEntitlementMemberError{} +var _ errors.UserError = &InvalidAttachmentMappedEntitlementMemberError{} + +func (*InvalidAttachmentMappedEntitlementMemberError) isSemanticError() {} + +func (*InvalidAttachmentMappedEntitlementMemberError) IsUserError() {} + +func (e *InvalidAttachmentMappedEntitlementMemberError) Error() string { + return "entitlement mapped members are not yet supported on attachments" +} + +func (e *InvalidAttachmentMappedEntitlementMemberError) StartPosition() ast.Position { + return e.Pos +} + +func (e *InvalidAttachmentMappedEntitlementMemberError) EndPosition(common.MemoryGauge) ast.Position { + return e.Pos +} + // InvalidNonEntitlementAccessError type InvalidNonEntitlementAccessError struct { ast.Range diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 540bfb6e08..9aab821faa 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -3843,7 +3843,8 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("in base", func(t *testing.T) { @@ -3897,7 +3898,7 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { ) }) - t.Run("in base, with entitlements", func(t *testing.T) { + t.Run("identity mapping in attachment", func(t *testing.T) { t.Parallel() @@ -3918,7 +3919,8 @@ func TestCheckAttachmentsExternalMutation(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("in self, through base", func(t *testing.T) { @@ -4081,7 +4083,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function self value cast", func(t *testing.T) { @@ -4122,7 +4125,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function self value cast invalid access", func(t *testing.T) { @@ -4163,9 +4167,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + require.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function base value cast", func(t *testing.T) { @@ -4205,7 +4209,8 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("entitlement mapped function base value cast invalid access", func(t *testing.T) { @@ -4245,8 +4250,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function self value access", func(t *testing.T) { @@ -4284,8 +4290,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) t.Run("entitlement mapped function base value access", func(t *testing.T) { @@ -4322,8 +4329,9 @@ func TestCheckAttachmentBaseNonMember(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + assert.IsType(t, &sema.InvalidAccessError{}, errs[1]) }) } diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 0c51f50297..3a65ede707 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -4760,7 +4760,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("invalid base and self in mapped functions", func(t *testing.T) { @@ -4785,10 +4786,11 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 3) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) + require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, "auth(Y) &S", typeMismatchErr.ExpectedType.QualifiedString(), @@ -4798,7 +4800,7 @@ func TestCheckAttachmentEntitlements(t *testing.T) { typeMismatchErr.ActualType.QualifiedString(), ) - require.ErrorAs(t, errs[1], &typeMismatchErr) + require.ErrorAs(t, errs[2], &typeMismatchErr) assert.Equal(t, "auth(Y) &A", typeMismatchErr.ExpectedType.QualifiedString(), @@ -4915,7 +4917,8 @@ func TestCheckAttachmentEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("access(all) decl", func(t *testing.T) { @@ -5187,7 +5190,7 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { ) }) - t.Run("base attachment access in mapped function", func(t *testing.T) { + t.Run("mapped function in attachment", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` @@ -5208,7 +5211,8 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { } `) - assert.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) }) t.Run("invalid base attachment access in mapped function", func(t *testing.T) { @@ -5232,10 +5236,11 @@ func TestCheckAttachmentAccessEntitlements(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) var typeMismatchErr *sema.TypeMismatchError - require.ErrorAs(t, errs[0], &typeMismatchErr) + require.ErrorAs(t, errs[1], &typeMismatchErr) assert.Equal(t, "auth(Y) &A?", typeMismatchErr.ExpectedType.QualifiedString(), diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 7afb08ec3b..fc9089b668 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/tests/checker" . "github.com/onflow/cadence/runtime/tests/utils" ) @@ -1996,7 +1997,7 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, _ := parseCheckAndInterpretWithOptions(t, ` entitlement E entitlement F entitlement G @@ -2027,7 +2028,15 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { destroy r return i } - `) + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) value, err := inter.Invoke("test") require.NoError(t, err) @@ -2045,7 +2054,7 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, _ := parseCheckAndInterpretWithOptions(t, ` entitlement E entitlement F entitlement mapping M { @@ -2074,7 +2083,15 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { destroy r return i } - `) + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.InvalidAttachmentMappedEntitlementMemberError{}, errs[0]) + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) value, err := inter.Invoke("test") require.NoError(t, err) From 9bf426822a7e642311affb09c0234a8970f7ef59 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 8 Nov 2023 12:05:33 -0500 Subject: [PATCH 43/52] fix tests --- runtime/tests/checker/events_test.go | 34 ++++------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 86d1b097fc..93fb69956d 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -1202,30 +1202,6 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { } } `) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) - }) - - t.Run("attachment with entitled base allowed", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - - attachment A for R { - require entitlement E - event ResourceDestroyed(name: Int = base.field) - } - - resource R { - access(E) let field : Int - - init() { - self.field = 3 - } - } - `) require.NoError(t, err) }) @@ -1236,11 +1212,7 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { _, err := ParseAndCheck(t, ` entitlement E - entitlement mapping M { - E -> E - } - - access(mapping M) attachment A for R { + access(all) attachment A for R { access(E) let field : Int event ResourceDestroyed(name: Int = self.field) init() { @@ -1248,7 +1220,9 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { } } - resource R {} + resource R { + access(E) fun foo() {} + } `) require.NoError(t, err) }) From 5851fbd405df56e6943f51f04dd3035f6be2d441 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:43:59 -0500 Subject: [PATCH 44/52] don't get access from function type --- runtime/interpreter/interpreter.go | 12 ++++++------ runtime/interpreter/value.go | 3 ++- runtime/sema/check_composite_declaration.go | 4 ++-- runtime/sema/checker.go | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index ca96ec2f94..d14e015ba0 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4767,25 +4767,25 @@ func (interpreter *Interpreter) ReportComputation(compKind common.ComputationKin } } -func (interpreter *Interpreter) getAccessOfMember(self Value, identifier string) *sema.Access { +func (interpreter *Interpreter) getAccessOfMember(self Value, identifier string) sema.Access { typ, err := interpreter.ConvertStaticToSemaType(self.StaticType(interpreter)) // some values (like transactions) do not have types that can be looked up this way. These types // do not support entitled members, so their access is always unauthorized if err != nil { - return &sema.UnauthorizedAccess + return sema.UnauthorizedAccess } member, hasMember := typ.GetMembers()[identifier] // certain values (like functions) have builtin members that are not present on the type // in such cases the access is always unauthorized if !hasMember { - return &sema.UnauthorizedAccess + return sema.UnauthorizedAccess } - return &member.Resolve(interpreter, identifier, ast.EmptyRange, func(err error) {}).Access + return member.Resolve(interpreter, identifier, ast.EmptyRange, func(err error) {}).Access } func (interpreter *Interpreter) mapMemberValueAuthorization( self Value, - memberAccess *sema.Access, + memberAccess sema.Access, resultValue Value, resultingType sema.Type, ) Value { @@ -4794,7 +4794,7 @@ func (interpreter *Interpreter) mapMemberValueAuthorization( return resultValue } - if mappedAccess, isMappedAccess := (*memberAccess).(*sema.EntitlementMapAccess); isMappedAccess { + if mappedAccess, isMappedAccess := (memberAccess).(*sema.EntitlementMapAccess); isMappedAccess { var auth Authorization switch selfValue := self.(type) { case AuthorizedValue: diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 618b20533c..c5086c23d7 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16802,7 +16802,8 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc var base *EphemeralReferenceValue var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { - functionAccess := function.FunctionType().Access + functionAccess := interpreter.getAccessOfMember(v, name) + // with respect to entitlements, any access inside an attachment that is not an entitlement access // does not provide any entitlements to base and self // E.g. consider: diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index b074784d86..2a7be35c1d 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1579,7 +1579,7 @@ func (checker *Checker) memberSatisfied( // Check access effectiveInterfaceMemberAccess := checker.effectiveInterfaceMemberAccess(interfaceMember.Access) - effectiveCompositeMemberAccess := checker.effectiveCompositeMemberAccess(compositeMember.Access) + effectiveCompositeMemberAccess := EffectiveCompositeMemberAccess(compositeMember.Access, checker.Config.AccessCheckMode) return !effectiveCompositeMemberAccess.IsLessPermissiveThan(effectiveInterfaceMemberAccess) } @@ -1911,7 +1911,7 @@ func (checker *Checker) enumMembersAndOrigins( // Enum cases must be effectively public enumAccess := checker.accessFromAstAccess(enumCase.Access) - if !checker.effectiveCompositeMemberAccess(enumAccess).Equal(PrimitiveAccess(ast.AccessAll)) { + if !EffectiveCompositeMemberAccess(enumAccess, checker.Config.AccessCheckMode).Equal(PrimitiveAccess(ast.AccessAll)) { checker.report( &InvalidAccessModifierError{ DeclarationKind: enumCase.DeclarationKind(), diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 99f5888197..2d6e468674 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2368,7 +2368,7 @@ func (checker *Checker) TypeActivationDepth() int { func (checker *Checker) effectiveMemberAccess(access Access, containerKind ContainerKind) Access { switch containerKind { case ContainerKindComposite: - return checker.effectiveCompositeMemberAccess(access) + return EffectiveCompositeMemberAccess(access, checker.Config.AccessCheckMode) case ContainerKindInterface: return checker.effectiveInterfaceMemberAccess(access) default: @@ -2384,12 +2384,12 @@ func (checker *Checker) effectiveInterfaceMemberAccess(access Access) Access { } } -func (checker *Checker) effectiveCompositeMemberAccess(access Access) Access { +func EffectiveCompositeMemberAccess(access Access, checkMode AccessCheckMode) Access { if !access.Equal(PrimitiveAccess(ast.AccessNotSpecified)) { return access } - switch checker.Config.AccessCheckMode { + switch checkMode { case AccessCheckModeStrict, AccessCheckModeNotSpecifiedRestricted: return PrimitiveAccess(ast.AccessSelf) From 4cc28d4a0f00e5886fe65c00f1c4b8dafc0c84fd Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 15:59:55 -0500 Subject: [PATCH 45/52] fix compile error --- runtime/interpreter/interpreter.go | 8 +++++++- runtime/sema/check_composite_declaration.go | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index d14e015ba0..cdb19838dd 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1000,8 +1000,14 @@ func (interpreter *Interpreter) evaluateDefaultDestroyEvent( var self MemberAccessibleValue = containingResourceComposite if containingResourceComposite.Kind == common.CompositeKindAttachment { + attachmentType := interpreter.MustSemaTypeOfValue(containingResourceComposite).(*sema.CompositeType) + var base *EphemeralReferenceValue - base, self = attachmentBaseAndSelfValues(declarationInterpreter, containingResourceComposite) + base, self = attachmentBaseAndSelfValues( + declarationInterpreter, + sema.NewAccessFromEntitlementSet(attachmentType.SupportedEntitlements(), sema.Conjunction), + containingResourceComposite, + ) declarationInterpreter.declareVariable(sema.BaseIdentifier, base) } declarationInterpreter.declareVariable(sema.SelfIdentifier, self) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 2a7be35c1d..715ce40e90 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2035,7 +2035,7 @@ func (checker *Checker) checkDefaultDestroyEventParam( paramType := param.TypeAnnotation.Type paramDefaultArgument := astParam.DefaultArgument - access := NewEntitlementSetAccessFromSet(containerType.SupportedEntitlements(), Conjunction) + access := NewAccessFromEntitlementSet(containerType.SupportedEntitlements(), Conjunction) // make `self` and `base` available when checking default arguments so the fields of the composite are available checker.declareSelfValue(access, containerType, containerDeclaration.DeclarationDocString()) From a5f43bc4f3bc9e5274b793ddfab2e7e2005b4ff8 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 16:08:31 -0500 Subject: [PATCH 46/52] Revert "function types carry access information" This reverts commit 6bcad92725f575c625eb2d292b11ef4672c97841. --- runtime/convertValues_test.go | 1 - runtime/interpreter/interpreter.go | 1 - runtime/interpreter/value_function_test.go | 2 - runtime/interpreter/value_test.go | 1 - runtime/sema/check_composite_declaration.go | 3 -- runtime/sema/checker.go | 2 - runtime/sema/crypto_algorithm_types.go | 2 - runtime/sema/meta_type.go | 1 - runtime/sema/runtime_type_constructors.go | 11 ---- runtime/sema/string_type.go | 11 ---- runtime/sema/type.go | 52 +------------------ runtime/sema/type_test.go | 2 - runtime/stdlib/account.go | 2 - runtime/stdlib/block.go | 2 - runtime/stdlib/log.go | 1 - runtime/stdlib/panic.go | 1 - runtime/stdlib/publickey.go | 1 - runtime/stdlib/random.go | 1 - runtime/tests/checker/interface_test.go | 1 - runtime/tests/interpreter/interface_test.go | 3 -- runtime/tests/interpreter/interpreter_test.go | 1 - runtime/tests/interpreter/runtimetype_test.go | 3 -- 22 files changed, 1 insertion(+), 104 deletions(-) diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 74a4721e58..f80e41330c 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -131,7 +131,6 @@ func TestRuntimeExportValue(t *testing.T) { testFunction := &interpreter.InterpretedFunctionValue{ Type: sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, nil, sema.VoidTypeAnnotation, ), diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index cac1ade5e7..aaa0d475f8 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3567,7 +3567,6 @@ func functionTypeFunction(invocation Invocation) Value { interpreter, sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, parameterTypes, sema.NewTypeAnnotation(returnType), ), diff --git a/runtime/interpreter/value_function_test.go b/runtime/interpreter/value_function_test.go index c40143298e..ffcfdfddd3 100644 --- a/runtime/interpreter/value_function_test.go +++ b/runtime/interpreter/value_function_test.go @@ -45,7 +45,6 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) @@ -72,7 +71,6 @@ func TestFunctionStaticType(t *testing.T) { hostFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, nil, sema.BoolTypeAnnotation, ) diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index 962a3d5221..cf79dfed79 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -3619,7 +3619,6 @@ func TestValue_ConformsToStaticType(t *testing.T) { functionType := sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, []sema.Parameter{ { TypeAnnotation: sema.IntTypeAnnotation, diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 2c6197b7bd..10594e47d0 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1292,13 +1292,11 @@ func (checker *Checker) checkCompositeLikeConformance( initializerType := NewSimpleFunctionType( compositeType.ConstructorPurity, - UnauthorizedAccess, compositeType.ConstructorParameters, VoidTypeAnnotation, ) interfaceInitializerType := NewSimpleFunctionType( conformance.InitializerPurity, - UnauthorizedAccess, conformance.InitializerParameters, VoidTypeAnnotation, ) @@ -2195,7 +2193,6 @@ func (checker *Checker) checkSpecialFunction( functionType := NewSimpleFunctionType( purity, - fnAccess, parameters, VoidTypeAnnotation, ) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 2d6e468674..c19a73de6b 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -132,7 +132,6 @@ var _ ast.ExpressionVisitor[Type] = &Checker{} var baseFunctionType = NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, nil, VoidTypeAnnotation, ) @@ -1129,7 +1128,6 @@ func (checker *Checker) convertFunctionType(t *ast.FunctionType) Type { return NewSimpleFunctionType( purity, - UnauthorizedAccess, parameters, returnTypeAnnotation, ) diff --git a/runtime/sema/crypto_algorithm_types.go b/runtime/sema/crypto_algorithm_types.go index a084b634d5..b74d326bff 100644 --- a/runtime/sema/crypto_algorithm_types.go +++ b/runtime/sema/crypto_algorithm_types.go @@ -110,7 +110,6 @@ const HashAlgorithmTypeHashFunctionName = "hash" var HashAlgorithmTypeHashFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -129,7 +128,6 @@ const HashAlgorithmTypeHashWithTagFunctionName = "hashWithTag" var HashAlgorithmTypeHashWithTagFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/meta_type.go b/runtime/sema/meta_type.go index 689f170eef..6ee455c1ab 100644 --- a/runtime/sema/meta_type.go +++ b/runtime/sema/meta_type.go @@ -50,7 +50,6 @@ var MetaTypeAnnotation = NewTypeAnnotation(MetaType) var MetaTypeIsSubtypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: "of", diff --git a/runtime/sema/runtime_type_constructors.go b/runtime/sema/runtime_type_constructors.go index f23779af26..86bec84979 100644 --- a/runtime/sema/runtime_type_constructors.go +++ b/runtime/sema/runtime_type_constructors.go @@ -26,7 +26,6 @@ type RuntimeTypeConstructor struct { var MetaTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -35,7 +34,6 @@ const OptionalTypeFunctionName = "OptionalType" var OptionalTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -50,7 +48,6 @@ const VariableSizedArrayTypeFunctionName = "VariableSizedArrayType" var VariableSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -65,7 +62,6 @@ const ConstantSizedArrayTypeFunctionName = "ConstantSizedArrayType" var ConstantSizedArrayTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "type", @@ -87,7 +83,6 @@ const DictionaryTypeFunctionName = "DictionaryType" var DictionaryTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -105,7 +100,6 @@ const CompositeTypeFunctionName = "CompositeType" var CompositeTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -120,7 +114,6 @@ const InterfaceTypeFunctionName = "InterfaceType" var InterfaceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -135,7 +128,6 @@ const FunctionTypeFunctionName = "FunctionType" var FunctionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "parameters", @@ -157,7 +149,6 @@ const IntersectionTypeFunctionName = "IntersectionType" var IntersectionTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "types", @@ -175,7 +166,6 @@ const ReferenceTypeFunctionName = "ReferenceType" var ReferenceTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "entitlements", @@ -197,7 +187,6 @@ const CapabilityTypeFunctionName = "CapabilityType" var CapabilityTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/string_type.go b/runtime/sema/string_type.go index 0d7ff70407..bcb562c6d6 100644 --- a/runtime/sema/string_type.go +++ b/runtime/sema/string_type.go @@ -128,7 +128,6 @@ func init() { var StringTypeConcatFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -147,7 +146,6 @@ Returns a new string which contains the given string concatenated to the end of var StringTypeSliceFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -194,7 +192,6 @@ var ByteArrayArrayTypeAnnotation = NewTypeAnnotation(ByteArrayArrayType) var StringTypeDecodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -222,7 +219,6 @@ The byte array of the UTF-8 encoding var StringTypeToLowerFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -249,7 +245,6 @@ var StringFunctionType = func() *FunctionType { functionType := NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -307,7 +302,6 @@ var StringFunctionType = func() *FunctionType { var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -320,7 +314,6 @@ var StringTypeEncodeHexFunctionType = NewSimpleFunctionType( var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -337,7 +330,6 @@ var StringTypeFromUtf8FunctionType = NewSimpleFunctionType( var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -352,7 +344,6 @@ var StringTypeFromCharactersFunctionType = NewSimpleFunctionType( var StringTypeJoinFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -371,7 +362,6 @@ var StringTypeJoinFunctionType = NewSimpleFunctionType( var StringTypeSplitFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "separator", @@ -387,7 +377,6 @@ var StringTypeSplitFunctionType = NewSimpleFunctionType( var StringTypeReplaceAllFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "of", diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 27be861d80..43f8e82bad 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -407,7 +407,6 @@ const IsInstanceFunctionName = "isInstance" var IsInstanceFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -428,7 +427,6 @@ const GetTypeFunctionName = "getType" var GetTypeFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, MetaTypeAnnotation, ) @@ -443,7 +441,6 @@ const ToStringFunctionName = "toString" var ToStringFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, StringTypeAnnotation, ) @@ -481,7 +478,6 @@ func FromStringFunctionDocstring(ty Type) string { func FromStringFunctionType(ty Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -511,7 +507,6 @@ func FromBigEndianBytesFunctionDocstring(ty Type) string { func FromBigEndianBytesFunctionType(ty Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -533,7 +528,6 @@ const ToBigEndianBytesFunctionName = "toBigEndianBytes" var ToBigEndianBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -816,7 +810,6 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { return &FunctionType{ Purity: functionPurity, - Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -827,7 +820,6 @@ func OptionalTypeMapFunctionType(typ Type) *FunctionType { TypeAnnotation: NewTypeAnnotation( &FunctionType{ Purity: functionPurity, - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -1034,7 +1026,6 @@ var SaturatingArithmeticTypeFunctionTypes = map[Type]*FunctionType{} func registerSaturatingArithmeticType(t Type) { SaturatingArithmeticTypeFunctionTypes[t] = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2382,7 +2373,6 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2391,7 +2381,6 @@ func ArrayRemoveLastFunctionType(elementType Type) *FunctionType { func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, nil, NewTypeAnnotation(elementType), ) @@ -2400,7 +2389,6 @@ func ArrayRemoveFirstFunctionType(elementType Type) *FunctionType { func ArrayRemoveFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2414,7 +2402,6 @@ func ArrayRemoveFunctionType(elementType Type) *FunctionType { func ArrayInsertFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Identifier: "at", @@ -2434,7 +2421,6 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { typeAnnotation := NewTypeAnnotation(arrayType) return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2449,7 +2435,6 @@ func ArrayConcatFunctionType(arrayType Type) *FunctionType { func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "of", @@ -2464,7 +2449,6 @@ func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { func ArrayContainsFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2479,7 +2463,6 @@ func ArrayContainsFunctionType(elementType Type) *FunctionType { func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2494,7 +2477,6 @@ func ArrayAppendAllFunctionType(arrayType Type) *FunctionType { func ArrayAppendFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2509,7 +2491,6 @@ func ArrayAppendFunctionType(elementType Type) *FunctionType { func ArraySliceFunctionType(elementType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "from", @@ -2529,7 +2510,6 @@ func ArraySliceFunctionType(elementType Type) *FunctionType { func ArrayReverseFunctionType(arrayType ArrayType) *FunctionType { return &FunctionType{ Parameters: []Parameter{}, - Access: UnauthorizedAccess, ReturnTypeAnnotation: NewTypeAnnotation(arrayType), Purity: FunctionPurityView, } @@ -2539,7 +2519,6 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * // fun filter(_ function: ((T): Bool)): [T] // funcType: elementType -> Bool funcType := &FunctionType{ - Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2551,7 +2530,6 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) * } return &FunctionType{ - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -2590,7 +2568,6 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * // transformFuncType: elementType -> U transformFuncType := &FunctionType{ - Access: UnauthorizedAccess, Parameters: []Parameter{ { Identifier: "element", @@ -2601,7 +2578,6 @@ func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) * } return &FunctionType{ - Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{ typeParameter, }, @@ -3179,7 +3155,6 @@ func (p FunctionPurity) String() string { type FunctionType struct { Purity FunctionPurity - Access Access ReturnTypeAnnotation TypeAnnotation Arity *Arity ArgumentExpressionsCheck ArgumentExpressionsCheck @@ -3193,13 +3168,11 @@ type FunctionType struct { func NewSimpleFunctionType( purity FunctionPurity, - access Access, parameters []Parameter, returnTypeAnnotation TypeAnnotation, ) *FunctionType { return &FunctionType{ Purity: purity, - Access: access, Parameters: parameters, ReturnTypeAnnotation: returnTypeAnnotation, } @@ -3553,7 +3526,6 @@ func (t *FunctionType) RewriteWithIntersectionTypes() (Type, bool) { return &FunctionType{ Purity: t.Purity, - Access: t.Access, TypeParameters: rewrittenTypeParameters, Parameters: rewrittenParameters, ReturnTypeAnnotation: NewTypeAnnotation(rewrittenReturnType), @@ -3671,7 +3643,6 @@ func (t *FunctionType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type return &FunctionType{ Purity: t.Purity, - Access: t.Access, Parameters: newParameters, ReturnTypeAnnotation: NewTypeAnnotation(newReturnType), Arity: t.Arity, @@ -3727,7 +3698,7 @@ func (t *FunctionType) Map(gauge common.MemoryGauge, typeParamMap map[*TypeParam returnType := t.ReturnTypeAnnotation.Map(gauge, typeParamMap, f) - functionType := NewSimpleFunctionType(t.Purity, t.Access, newParameters, returnType) + functionType := NewSimpleFunctionType(t.Purity, newParameters, returnType) functionType.TypeParameters = newTypeParameters return f(functionType) } @@ -4051,7 +4022,6 @@ func init() { func NumberConversionFunctionType(numberType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4086,7 +4056,6 @@ func baseFunctionVariable(name string, ty *FunctionType, docString string) *Vari var AddressConversionFunctionType = &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4117,7 +4086,6 @@ Returns an Address from the given byte array var AddressTypeFromBytesFunctionType = &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, Parameters: []Parameter{ { Label: ArgumentLabelNotRequired, @@ -4223,7 +4191,6 @@ func numberFunctionArgumentExpressionsChecker(targetType Type) ArgumentExpressio func pathConversionFunctionType(pathType Type) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "identifier", @@ -4260,7 +4227,6 @@ func init() { typeName, &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, TypeParameters: []*TypeParameter{{Name: "T"}}, ReturnTypeAnnotation: MetaTypeAnnotation, }, @@ -4748,7 +4714,6 @@ func CompositeForEachAttachmentFunctionType(t common.CompositeKind) *FunctionTyp Identifier: "f", TypeAnnotation: NewTypeAnnotation( &FunctionType{ - Access: UnauthorizedAccess, Parameters: []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -4851,7 +4816,6 @@ func (t *CompositeType) SetNestedType(name string, nestedType ContainedType) { func (t *CompositeType) ConstructorFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, - Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: NewTypeAnnotation(t), @@ -4861,7 +4825,6 @@ func (t *CompositeType) ConstructorFunctionType() *FunctionType { func (t *CompositeType) InitializerFunctionType() *FunctionType { return &FunctionType{ IsConstructor: true, - Access: UnauthorizedAccess, Purity: t.ConstructorPurity, Parameters: t.ConstructorParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -5862,7 +5825,6 @@ func (t *DictionaryType) initializeMemberResolvers() { func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -5877,7 +5839,6 @@ func DictionaryContainsKeyFunctionType(t *DictionaryType) *FunctionType { func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5900,7 +5861,6 @@ func DictionaryInsertFunctionType(t *DictionaryType) *FunctionType { func DictionaryRemoveFunctionType(t *DictionaryType) *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5921,7 +5881,6 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun(K): Bool funcType := NewSimpleFunctionType( functionPurity, - UnauthorizedAccess, []Parameter{ { Identifier: "key", @@ -5934,7 +5893,6 @@ func DictionaryForEachKeyFunctionType(t *DictionaryType) *FunctionType { // fun forEachKey(_ function: fun(K): Bool): Void return NewSimpleFunctionType( functionPurity, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, @@ -6375,7 +6333,6 @@ const AddressTypeToBytesFunctionName = `toBytes` var AddressTypeToBytesFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, nil, ByteArrayTypeAnnotation, ) @@ -6864,7 +6821,6 @@ var _ Type = &TransactionType{} func (t *TransactionType) EntryPointFunctionType() *FunctionType { return NewSimpleFunctionType( FunctionPurityImpure, - UnauthorizedAccess, append(t.Parameters, t.PrepareParameters...), VoidTypeAnnotation, ) @@ -6873,7 +6829,6 @@ func (t *TransactionType) EntryPointFunctionType() *FunctionType { func (t *TransactionType) PrepareFunctionType() *FunctionType { return &FunctionType{ Purity: FunctionPurityImpure, - Access: UnauthorizedAccess, IsConstructor: true, Parameters: t.PrepareParameters, ReturnTypeAnnotation: VoidTypeAnnotation, @@ -6882,7 +6837,6 @@ func (t *TransactionType) PrepareFunctionType() *FunctionType { var transactionTypeExecuteFunctionType = &FunctionType{ Purity: FunctionPurityImpure, - Access: UnauthorizedAccess, IsConstructor: true, ReturnTypeAnnotation: VoidTypeAnnotation, } @@ -7508,7 +7462,6 @@ func CapabilityTypeBorrowFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: NewTypeAnnotation( &OptionalType{ @@ -7530,7 +7483,6 @@ func CapabilityTypeCheckFunctionType(borrowType Type) *FunctionType { return &FunctionType{ Purity: FunctionPurityView, - Access: UnauthorizedAccess, TypeParameters: typeParameters, ReturnTypeAnnotation: BoolTypeAnnotation, } @@ -7761,7 +7713,6 @@ var PublicKeyArrayTypeAnnotation = NewTypeAnnotation(PublicKeyArrayType) var PublicKeyVerifyFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Identifier: "signature", @@ -7785,7 +7736,6 @@ var PublicKeyVerifyFunctionType = NewSimpleFunctionType( var PublicKeyVerifyPoPFunctionType = NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { Label: ArgumentLabelNotRequired, diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index d3fed2e804..ac157de230 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -1988,7 +1988,6 @@ func TestMapType(t *testing.T) { } original := NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( @@ -2016,7 +2015,6 @@ func TestMapType(t *testing.T) { } mapped := NewSimpleFunctionType( FunctionPurityView, - UnauthorizedAccess, []Parameter{ { TypeAnnotation: NewTypeAnnotation( diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index f53c2c07b7..a30d3784b1 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -43,7 +43,6 @@ Creates a new account, paid by the given existing account // auth(Storage, Contracts, Keys, Inbox, Capabilities) &Account var accountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: "payer", @@ -2040,7 +2039,6 @@ Returns the account for the given address var getAccountFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/block.go b/runtime/stdlib/block.go index 457e80d956..9edaf3a626 100644 --- a/runtime/stdlib/block.go +++ b/runtime/stdlib/block.go @@ -34,7 +34,6 @@ Returns the current block, i.e. the block which contains the currently executed var getCurrentBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, nil, sema.BlockTypeAnnotation, ) @@ -45,7 +44,6 @@ Returns the block at the given height. If the given block does not exist the fun var getBlockFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: "at", diff --git a/runtime/stdlib/log.go b/runtime/stdlib/log.go index 389ae0a3c2..792f8273df 100644 --- a/runtime/stdlib/log.go +++ b/runtime/stdlib/log.go @@ -26,7 +26,6 @@ import ( var LogFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/panic.go b/runtime/stdlib/panic.go index 703dbe61a2..d9f6296780 100644 --- a/runtime/stdlib/panic.go +++ b/runtime/stdlib/panic.go @@ -45,7 +45,6 @@ Terminates the program unconditionally and reports a message which explains why var panicFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/stdlib/publickey.go b/runtime/stdlib/publickey.go index c26768b628..069fa27989 100644 --- a/runtime/stdlib/publickey.go +++ b/runtime/stdlib/publickey.go @@ -32,7 +32,6 @@ Constructs a new public key var publicKeyConstructorFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Identifier: sema.PublicKeyTypePublicKeyFieldName, diff --git a/runtime/stdlib/random.go b/runtime/stdlib/random.go index 20a621457d..7340db4559 100644 --- a/runtime/stdlib/random.go +++ b/runtime/stdlib/random.go @@ -36,7 +36,6 @@ Follow best practices to prevent security issues when using this function var unsafeRandomFunctionType = sema.NewSimpleFunctionType( sema.FunctionPurityImpure, - sema.UnauthorizedAccess, nil, sema.UInt64TypeAnnotation, ) diff --git a/runtime/tests/checker/interface_test.go b/runtime/tests/checker/interface_test.go index 5acae0ba8f..0eae0fcd4e 100644 --- a/runtime/tests/checker/interface_test.go +++ b/runtime/tests/checker/interface_test.go @@ -1772,7 +1772,6 @@ func TestCheckInvalidInterfaceUseAsTypeSuggestion(t *testing.T) { assert.Equal(t, &sema.FunctionType{ - Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.NewTypeAnnotation( diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index 937bc7edde..a66b429611 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -563,7 +563,6 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -676,7 +675,6 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, @@ -789,7 +787,6 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { logFunctionType := sema.NewSimpleFunctionType( sema.FunctionPurityView, - sema.UnauthorizedAccess, []sema.Parameter{ { Label: sema.ArgumentLabelNotRequired, diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index b8780d769a..31a1e05804 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -9893,7 +9893,6 @@ func TestInterpretHostFunctionStaticType(t *testing.T) { nil, &sema.FunctionType{ Purity: sema.FunctionPurityView, - Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.MetaTypeAnnotation, }, ), diff --git a/runtime/tests/interpreter/runtimetype_test.go b/runtime/tests/interpreter/runtimetype_test.go index f2320f72b8..d3411a8880 100644 --- a/runtime/tests/interpreter/runtimetype_test.go +++ b/runtime/tests/interpreter/runtimetype_test.go @@ -410,7 +410,6 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ - Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ { TypeAnnotation: sema.StringTypeAnnotation, @@ -427,7 +426,6 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ - Access: sema.UnauthorizedAccess, Parameters: []sema.Parameter{ {TypeAnnotation: sema.StringTypeAnnotation}, {TypeAnnotation: sema.IntTypeAnnotation}, @@ -443,7 +441,6 @@ func TestInterpretFunctionType(t *testing.T) { interpreter.TypeValue{ Type: interpreter.FunctionStaticType{ Type: &sema.FunctionType{ - Access: sema.UnauthorizedAccess, ReturnTypeAnnotation: sema.StringTypeAnnotation, }, }, From 6390ffb3a94bf276f14310ecd49c0fc900c6a55a Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 16:09:50 -0500 Subject: [PATCH 47/52] Revert "add missing access" This reverts commit 9369f0be038ace1f32b14bc8d8ce54683bbff82c. --- runtime/sema/checker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index c19a73de6b..c86b18e33c 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1352,7 +1352,6 @@ func (checker *Checker) functionType( return &FunctionType{ Purity: PurityFromAnnotation(purity), - Access: access, TypeParameters: convertedTypeParameters, Parameters: convertedParameters, ReturnTypeAnnotation: convertedReturnTypeAnnotation, From 4c0c9ec255500cf1ff5673f76d7c6938beec70f5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 27 Nov 2023 16:09:54 -0500 Subject: [PATCH 48/52] Revert "fix test" This reverts commit f2e169b1abf50233a53544d7b39782de9a0a954e. --- runtime/tests/checker/function_test.go | 1 - runtime/tests/checker/member_test.go | 2 -- 2 files changed, 3 deletions(-) diff --git a/runtime/tests/checker/function_test.go b/runtime/tests/checker/function_test.go index bfff43f279..5e1facb8cb 100644 --- a/runtime/tests/checker/function_test.go +++ b/runtime/tests/checker/function_test.go @@ -537,7 +537,6 @@ func TestCheckNativeFunctionDeclaration(t *testing.T) { assert.Equal(t, sema.NewTypeAnnotation(&sema.FunctionType{ - Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{ { Identifier: "foo", diff --git a/runtime/tests/checker/member_test.go b/runtime/tests/checker/member_test.go index 0114187214..ea2b592220 100644 --- a/runtime/tests/checker/member_test.go +++ b/runtime/tests/checker/member_test.go @@ -25,7 +25,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" ) @@ -275,7 +274,6 @@ func TestCheckFunctionTypeReceiverType(t *testing.T) { assert.Equal(t, &sema.FunctionType{ Purity: sema.FunctionPurityImpure, - Access: sema.PrimitiveAccess(ast.AccessNotSpecified), Parameters: []sema.Parameter{}, ReturnTypeAnnotation: sema.VoidTypeAnnotation, }, From cba1f2c1ecf92e99d92d2ff8de4d2e46f558ece0 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 29 Nov 2023 11:38:22 -0500 Subject: [PATCH 49/52] Apply suggestions from code review Co-authored-by: Supun Setunga --- runtime/sema/access.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/sema/access.go b/runtime/sema/access.go index ae6d976ad4..3c05b4673a 100644 --- a/runtime/sema/access.go +++ b/runtime/sema/access.go @@ -88,6 +88,7 @@ func NewAccessFromEntitlementSet( } func (EntitlementSetAccess) isAccess() {} + func (EntitlementSetAccess) IsPrimitiveAccess() bool { return false } @@ -283,6 +284,7 @@ func NewEntitlementMapAccess(mapType *EntitlementMapType) *EntitlementMapAccess } func (*EntitlementMapAccess) isAccess() {} + func (*EntitlementMapAccess) IsPrimitiveAccess() bool { return false } @@ -455,6 +457,7 @@ type PrimitiveAccess ast.PrimitiveAccess var _ Access = PrimitiveAccess(0) func (PrimitiveAccess) isAccess() {} + func (PrimitiveAccess) IsPrimitiveAccess() bool { return true } From 2007e28249d98b3d8997bfc1166a9b17b7f596b6 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 29 Nov 2023 11:40:37 -0500 Subject: [PATCH 50/52] remove unnecessary refactor --- runtime/sema/check_composite_declaration.go | 4 ++-- runtime/sema/checker.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 10594e47d0..bab358032b 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1577,7 +1577,7 @@ func (checker *Checker) memberSatisfied( // Check access effectiveInterfaceMemberAccess := checker.effectiveInterfaceMemberAccess(interfaceMember.Access) - effectiveCompositeMemberAccess := EffectiveCompositeMemberAccess(compositeMember.Access, checker.Config.AccessCheckMode) + effectiveCompositeMemberAccess := checker.EffectiveCompositeMemberAccess(compositeMember.Access) return !effectiveCompositeMemberAccess.IsLessPermissiveThan(effectiveInterfaceMemberAccess) } @@ -1909,7 +1909,7 @@ func (checker *Checker) enumMembersAndOrigins( // Enum cases must be effectively public enumAccess := checker.accessFromAstAccess(enumCase.Access) - if !EffectiveCompositeMemberAccess(enumAccess, checker.Config.AccessCheckMode).Equal(PrimitiveAccess(ast.AccessAll)) { + if !checker.EffectiveCompositeMemberAccess(enumAccess).Equal(PrimitiveAccess(ast.AccessAll)) { checker.report( &InvalidAccessModifierError{ DeclarationKind: enumCase.DeclarationKind(), diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index c86b18e33c..fd7af1d8ed 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2365,7 +2365,7 @@ func (checker *Checker) TypeActivationDepth() int { func (checker *Checker) effectiveMemberAccess(access Access, containerKind ContainerKind) Access { switch containerKind { case ContainerKindComposite: - return EffectiveCompositeMemberAccess(access, checker.Config.AccessCheckMode) + return checker.EffectiveCompositeMemberAccess(access) case ContainerKindInterface: return checker.effectiveInterfaceMemberAccess(access) default: @@ -2381,12 +2381,12 @@ func (checker *Checker) effectiveInterfaceMemberAccess(access Access) Access { } } -func EffectiveCompositeMemberAccess(access Access, checkMode AccessCheckMode) Access { +func (checker *Checker) EffectiveCompositeMemberAccess(access Access) Access { if !access.Equal(PrimitiveAccess(ast.AccessNotSpecified)) { return access } - switch checkMode { + switch checker.Config.AccessCheckMode { case AccessCheckModeStrict, AccessCheckModeNotSpecifiedRestricted: return PrimitiveAccess(ast.AccessSelf) From 5c8a88f3f5b0538a46d2b9ea3a171cf0eaf01757 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 5 Dec 2023 10:31:44 -0500 Subject: [PATCH 51/52] respond to review --- runtime/ast/attachment.go | 8 ++------ runtime/interpreter/interpreter.go | 16 +++++++++++++--- runtime/interpreter/interpreter_expression.go | 6 ++++++ runtime/interpreter/value.go | 4 ++-- runtime/sema/check_composite_declaration.go | 2 +- runtime/sema/type.go | 5 +++-- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/runtime/ast/attachment.go b/runtime/ast/attachment.go index c16005db4d..b0cbe2b67b 100644 --- a/runtime/ast/attachment.go +++ b/runtime/ast/attachment.go @@ -255,10 +255,7 @@ const attachExpressionDoc = prettier.Text("attach") const attachExpressionToDoc = prettier.Text("to") func (e *AttachExpression) Doc() prettier.Doc { - var doc prettier.Concat - - doc = append( - doc, + return prettier.Concat{ attachExpressionDoc, prettier.Space, e.Attachment.Doc(), @@ -266,8 +263,7 @@ func (e *AttachExpression) Doc() prettier.Doc { attachExpressionToDoc, prettier.Space, e.Base.Doc(), - ) - return doc + } } func (e *AttachExpression) StartPosition() Position { diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index aaa0d475f8..635e8e7a81 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1000,7 +1000,11 @@ func (interpreter *Interpreter) evaluateDefaultDestroyEvent( if containingResourceComposite.Kind == common.CompositeKindAttachment { var base *EphemeralReferenceValue // in evaluation of destroy events, base and self are fully entitled, as the value must be owned - supportedEntitlements := interpreter.MustSemaTypeOfValue(containingResourceComposite).(*sema.CompositeType).SupportedEntitlements() + entitlementSupportingType, ok := interpreter.MustSemaTypeOfValue(containingResourceComposite).(sema.EntitlementSupportingType) + if !ok { + panic(errors.NewUnreachableError()) + } + supportedEntitlements := entitlementSupportingType.SupportedEntitlements() access := sema.NewAccessFromEntitlementSet(supportedEntitlements, sema.Conjunction) base, self = attachmentBaseAndSelfValues(declarationInterpreter, access, containingResourceComposite) declarationInterpreter.declareVariable(sema.BaseIdentifier, base) @@ -1009,7 +1013,7 @@ func (interpreter *Interpreter) evaluateDefaultDestroyEvent( for _, parameter := range parameters { // "lazily" evaluate the default argument expressions. - // This "lazy" with respect to the event's declaration: + // This is "lazy" with respect to the event's declaration: // if we declare a default event `ResourceDestroyed(foo: Int = self.x)`, // `self.x` is evaluated in the context that exists when the event is destroyed, // not the context when it is declared. This function is only called after the destroy @@ -1361,7 +1365,13 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( // set the base to the implicitly provided value, and remove this implicit argument from the list implicitArgumentPos := len(invocation.Arguments) - 1 invocation.Base = invocation.Arguments[implicitArgumentPos].(*EphemeralReferenceValue) - value.base = invocation.Base.Value.(*CompositeValue) + + var ok bool + value.base, ok = invocation.Base.Value.(*CompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + invocation.Arguments[implicitArgumentPos] = nil invocation.Arguments = invocation.Arguments[:implicitArgumentPos] invocation.ArgumentTypes[implicitArgumentPos] = nil diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 20a18c0f2a..3d78a23ed5 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1183,6 +1183,12 @@ func (interpreter *Interpreter) VisitCastingExpression(expression *ast.CastingEx // if the value itself has a mapped entitlement type in its authorization // (e.g. if it is a reference to `self` or `base` in an attachment function with mapped access) // substitution must also be performed on its entitlements + // + // we do this here (as opposed to in `IsSubTypeOfSemaType`) because casting is the only way that + // an entitlement can "traverse the boundary", so to speak, between runtime and static types, and + // thus this is the only place where it becomes necessary to "instantiate" the result of a map to its + // concrete outputs. In other places (e.g. interface conformance checks) we want to leave maps generic, + // so we don't substitute them. valueSemaType := interpreter.substituteMappedEntitlements(interpreter.MustSemaTypeOfValue(value)) valueStaticType := ConvertSemaToStaticType(interpreter, valueSemaType) isSubType := interpreter.IsSubTypeOfSemaType(valueStaticType, expectedType) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c5086c23d7..12b922de5d 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17706,7 +17706,7 @@ func NewEnumCaseValue( return v } -func (v *CompositeValue) getBaseValue(interpreter *Interpreter, fnAuth Authorization) *EphemeralReferenceValue { +func (v *CompositeValue) getBaseValue(interpreter *Interpreter, functionAuthorization Authorization) *EphemeralReferenceValue { attachmentType, ok := interpreter.MustSemaTypeOfValue(v).(*sema.CompositeType) if !ok { panic(errors.NewUnreachableError()) @@ -17720,7 +17720,7 @@ func (v *CompositeValue) getBaseValue(interpreter *Interpreter, fnAuth Authoriza baseType = ty } - return NewEphemeralReferenceValue(interpreter, fnAuth, v.base, baseType) + return NewEphemeralReferenceValue(interpreter, functionAuthorization, v.base, baseType) } func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeValue) { diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index bab358032b..d87bf49b7f 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -132,11 +132,11 @@ func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDe checker.visitCompositeLikeDeclaration(declaration) attachmentType := checker.Elaboration.CompositeDeclarationType(declaration) + checker.checkAttachmentMembersAccess(attachmentType) checker.checkAttachmentBaseType( attachmentType, declaration.BaseType, ) - checker.checkAttachmentMembersAccess(attachmentType) return } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 43f8e82bad..de830a6068 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4509,10 +4509,11 @@ func (t *CompositeType) SupportedEntitlements() (set *EntitlementOrderedSet) { set.SetAll(it.SupportedEntitlements()) }) - // attachments support at least the entitlements supported by their base + // attachments support at least the entitlements supported by their base, + // and we must ensure there is no recursive case if entitlementSupportingBase, isEntitlementSupportingBase := - // must ensure there is no recursive case t.GetBaseType().(EntitlementSupportingType); isEntitlementSupportingBase && entitlementSupportingBase != t { + set.SetAll(entitlementSupportingBase.SupportedEntitlements()) } From c4c8e0afc07f91a3cde5311e99767a842eeac107 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 6 Dec 2023 11:13:09 -0500 Subject: [PATCH 52/52] fix test --- runtime/tests/interpreter/attachments_test.go | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index fc9089b668..9426619add 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -2038,16 +2038,8 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { }, }) - value, err := inter.Invoke("test") - require.NoError(t, err) - - require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(3), - value.(*interpreter.EphemeralReferenceValue).Value, - ) + _, err := inter.Invoke("test") + require.ErrorAs(t, err, &interpreter.ValueTransferTypeError{}) }) t.Run("mapped base cast", func(t *testing.T) { @@ -2093,16 +2085,8 @@ func TestInterpretAttachmentMappedMembers(t *testing.T) { }, }) - value, err := inter.Invoke("test") - require.NoError(t, err) - - require.IsType(t, &interpreter.EphemeralReferenceValue{}, value) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(3), - value.(*interpreter.EphemeralReferenceValue).Value, - ) + _, err := inter.Invoke("test") + require.ErrorAs(t, err, &interpreter.ValueTransferTypeError{}) }) }