diff --git a/lint/warn_implicit_capability_leak.go b/lint/warn_implicit_capability_leak.go new file mode 100644 index 00000000..e5b9f2e8 --- /dev/null +++ b/lint/warn_implicit_capability_leak.go @@ -0,0 +1,87 @@ +/* + * Cadence-lint - The Cadence linter + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lint + +import ( + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/tools/analysis" +) + +var ImplicitCapabilityLeak = (func() *analysis.Analyzer { + + elementFilter := []ast.Element{ + (*ast.FieldDeclaration)(nil), + } + + return &analysis.Analyzer{ + Description: "Detects potential capability leak via public fields", + Requires: []*analysis.Analyzer{ + analysis.InspectorAnalyzer, + }, + Run: func(pass *analysis.Pass) interface{} { + inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector) + + location := pass.Program.Location + report := pass.Report + + inspector.Preorder( + elementFilter, + func(element ast.Element) { + fieldDeclaration, ok := element.(*ast.FieldDeclaration) + if !ok { + return + } + if fieldDeclaration.DeclarationAccess() != ast.AccessPublic { + return + } + + arr, ok := fieldDeclaration.TypeAnnotation.Type.(*ast.VariableSizedType) + if !ok { + return + } + nominalType, ok := arr.Type.(*ast.NominalType) + if !ok { + return + } + if nominalType.Identifier.Identifier != "Capability" { + return + } + + report( + analysis.Diagnostic{ + Location: location, + Range: ast.NewRangeFromPositioned(nil, element), + Category: ReplacementCategory, + Message: "capability might be leaking", + }, + ) + }, + ) + + return nil + }, + } +})() + +func init() { + RegisterAnalyzer( + "implicit-capability-leak", + ImplicitCapabilityLeak, + ) +} diff --git a/lint/warn_implicit_capability_leak_test.go b/lint/warn_implicit_capability_leak_test.go new file mode 100644 index 00000000..0e9168e4 --- /dev/null +++ b/lint/warn_implicit_capability_leak_test.go @@ -0,0 +1,91 @@ +/* + * Cadence-lint - The Cadence linter + * + * Copyright 2019-2022 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lint_test + +import ( + "testing" + + "github.com/onflow/cadence-tools/lint" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/tools/analysis" + "github.com/stretchr/testify/require" +) + +func TestImplicitCapabilityLeakViaArray(t *testing.T) { + + t.Parallel() + + t.Run("invalid", func(t *testing.T) { + + t.Parallel() + + diagnostics := testAnalyzers(t, + ` + pub contract MyContract { + pub let myCapArray: [Capability] + + init() { + self.myCapArray = [] + } + } + `, + lint.ImplicitCapabilityLeak, + ) + + require.Equal( + t, + []analysis.Diagnostic{ + { + Range: ast.Range{ + StartPos: ast.Position{Offset: 37, Line: 3, Column: 7}, + EndPos: ast.Position{Offset: 68, Line: 3, Column: 38}, + }, + Location: testLocation, + Category: lint.ReplacementCategory, + Message: "capability might be leaking", + }, + }, + diagnostics, + ) + }) + + t.Run("valid", func(t *testing.T) { + + t.Parallel() + + diagnostics := testAnalyzers(t, + ` + pub contract MyContract { + priv let myCapArray: [Capability] + + init() { + self.myCapArray = [] + } + } + `, + lint.ImplicitCapabilityLeak, + ) + + require.Equal( + t, + []analysis.Diagnostic(nil), + diagnostics, + ) + }) +}