From 3fb25b9de7f5673fe2af04606c7b35bb53f1f2bf Mon Sep 17 00:00:00 2001
From: Teddy Wing
Date: Thu, 18 May 2023 05:55:01 +0200
Subject: Rename package to "capturedrefrace"
I decided I don't like the "go" prefix for a Go package name. It doesn't
make sense. Rename the module and package accordingly.
I do like the "go" prefix in the command line program, so leave it
there.
---
capturedrefrace.go | 226 ++++++++++++++++++++++++++++++++++++++++++
capturedrefrace_test.go | 33 ++++++
cmd/gocapturedrefrace/main.go | 4 +-
go.mod | 2 +-
gocapturedrefrace.go | 226 ------------------------------------------
gocapturedrefrace_test.go | 33 ------
6 files changed, 262 insertions(+), 262 deletions(-)
create mode 100644 capturedrefrace.go
create mode 100644 capturedrefrace_test.go
delete mode 100644 gocapturedrefrace.go
delete mode 100644 gocapturedrefrace_test.go
diff --git a/capturedrefrace.go b/capturedrefrace.go
new file mode 100644
index 0000000..21a8b2d
--- /dev/null
+++ b/capturedrefrace.go
@@ -0,0 +1,226 @@
+// Copyright (c) 2023 Teddy Wing
+//
+// This file is part of Gocapturedrefrace.
+//
+// Gocapturedrefrace is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// Gocapturedrefrace is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Gocapturedrefrace. If not, see
+// .
+
+// TODO: package documentation.
+package capturedrefrace
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+var version = "0.0.1"
+
+var Analyzer = &analysis.Analyzer{
+ Name: "capturedrefrace",
+ Doc: "reports captured references in goroutine closures",
+ Run: run,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+ nodeFilter := []ast.Node{
+ (*ast.GoStmt)(nil),
+ }
+
+ inspect.Preorder(
+ nodeFilter,
+ func(node ast.Node) {
+ // Find `go` statements.
+ goStmt, ok := node.(*ast.GoStmt)
+ if !ok {
+ return
+ }
+
+ // Look for a function literal after the `go` statement.
+ funcLit, ok := goStmt.Call.Fun.(*ast.FuncLit)
+ if !ok {
+ return
+ }
+
+ // Inspect closure argument list.
+ for _, arg := range funcLit.Type.Params.List {
+ // Report reference arguments.
+ _, ok := arg.Type.(*ast.StarExpr)
+ if !ok {
+ continue
+ }
+
+ pass.Reportf(
+ arg.Pos(),
+ "reference %s in goroutine closure",
+ arg.Names[0],
+ )
+ }
+
+ checkClosure(pass, funcLit)
+ },
+ )
+
+ return nil, nil
+}
+
+// checkClosure reports variables used in funcLit that are captured from an
+// outer scope.
+func checkClosure(pass *analysis.Pass, funcLit *ast.FuncLit) {
+ // Get the closure's scope.
+ funcScope := pass.TypesInfo.Scopes[funcLit.Type]
+
+ // Build a list of assignments local to funcLit. These will be ignored as
+ // shadowed variables.
+ localVarDeclarations := findLocalVarDeclarations(funcLit)
+
+ ast.Inspect(
+ funcLit,
+ func(node ast.Node) bool {
+ ident, ok := node.(*ast.Ident)
+ if !ok {
+ return true
+ }
+
+ if ident.Obj == nil {
+ return true
+ }
+
+ // Ignore shadowed variables.
+ for _, declarationIdent := range localVarDeclarations {
+ if ident == declarationIdent {
+ return true
+ }
+ }
+
+ // Find out whether `ident` was defined in an outer scope.
+ scope, scopeObj := funcScope.LookupParent(ident.Name, ident.NamePos)
+
+ // Identifier is local to the closure.
+ if scope == nil && scopeObj == nil {
+ return true
+ }
+
+ // Ignore non-variable identifiers.
+ variable, ok := scopeObj.(*types.Var)
+ if !ok {
+ return true
+ }
+
+ // Ignore captured callable variables, like function arguments.
+ _, isVariableTypeSignature := variable.Type().(*types.Signature)
+ if isVariableTypeSignature {
+ return true
+ }
+
+ // Identifier was defined in a different scope.
+ if funcScope != scope {
+ pass.Reportf(
+ ident.Pos(),
+ "captured reference %s in goroutine closure",
+ ident,
+ )
+ }
+
+ return true
+ },
+ )
+}
+
+// findLocalVarDeclarations returns a list of all variable declarations in
+// funcLit.
+func findLocalVarDeclarations(
+ funcLit *ast.FuncLit,
+) (declarations []*ast.Ident) {
+ declarations = []*ast.Ident{}
+
+ ast.Inspect(
+ funcLit,
+ func(node ast.Node) bool {
+ switch node := node.(type) {
+ case *ast.AssignStmt:
+ assignments := assignmentDefinitions(node)
+ declarations = append(declarations, assignments...)
+
+ case *ast.GenDecl:
+ decls := varDeclarations(node)
+ if decls != nil {
+ declarations = append(declarations, decls...)
+ }
+ }
+
+ return true
+ },
+ )
+
+ return declarations
+}
+
+// assignmentDefinitions returns the identifiers corresponding to variable
+// assignments in assignStmt, or nil if assignStmt does not declare any
+// variables.
+func assignmentDefinitions(
+ assignStmt *ast.AssignStmt,
+) (assignments []*ast.Ident) {
+ assignments = []*ast.Ident{}
+
+ if assignStmt.Tok != token.DEFINE {
+ return nil
+ }
+
+ for _, lhs := range assignStmt.Lhs {
+ ident, ok := lhs.(*ast.Ident)
+ if !ok {
+ continue
+ }
+
+ if ident == nil {
+ continue
+ }
+
+ assignments = append(assignments, ident)
+ }
+
+ return assignments
+}
+
+// varDeclarations returns the identifiers corresponding to variable
+// declarations in decl, or nil if decl is not a variable declaration.
+func varDeclarations(decl *ast.GenDecl) (declarations []*ast.Ident) {
+ if decl.Tok != token.VAR {
+ return nil
+ }
+
+ declarations = []*ast.Ident{}
+
+ for _, spec := range decl.Specs {
+ valueSpec, ok := spec.(*ast.ValueSpec)
+ if !ok {
+ return declarations
+ }
+
+ for _, ident := range valueSpec.Names {
+ declarations = append(declarations, ident)
+ }
+ }
+
+ return declarations
+}
diff --git a/capturedrefrace_test.go b/capturedrefrace_test.go
new file mode 100644
index 0000000..f4bb43d
--- /dev/null
+++ b/capturedrefrace_test.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2023 Teddy Wing
+//
+// This file is part of Gocapturedrefrace.
+//
+// Gocapturedrefrace is free software: you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// Gocapturedrefrace is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Gocapturedrefrace. If not, see
+// .
+
+
+package capturedrefrace_test
+
+import (
+ "testing"
+
+ "golang.org/x/tools/go/analysis/analysistest"
+ "gopkg.teddywing.com/capturedrefrace"
+)
+
+func Test(t *testing.T) {
+ testdata := analysistest.TestData()
+
+ analysistest.Run(t, testdata, capturedrefrace.Analyzer, ".")
+}
diff --git a/cmd/gocapturedrefrace/main.go b/cmd/gocapturedrefrace/main.go
index bab8032..60c7838 100644
--- a/cmd/gocapturedrefrace/main.go
+++ b/cmd/gocapturedrefrace/main.go
@@ -21,9 +21,9 @@ package main
import (
"golang.org/x/tools/go/analysis/singlechecker"
- "gopkg.teddywing.com/gocapturedrefrace"
+ "gopkg.teddywing.com/capturedrefrace"
)
func main() {
- singlechecker.Main(gocapturedrefrace.Analyzer)
+ singlechecker.Main(capturedrefrace.Analyzer)
}
diff --git a/go.mod b/go.mod
index 2a93e77..5d08fa1 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module gopkg.teddywing.com/gocapturedrefrace
+module gopkg.teddywing.com/capturedrefrace
go 1.20
diff --git a/gocapturedrefrace.go b/gocapturedrefrace.go
deleted file mode 100644
index a3c1699..0000000
--- a/gocapturedrefrace.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) 2023 Teddy Wing
-//
-// This file is part of Gocapturedrefrace.
-//
-// Gocapturedrefrace is free software: you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// Gocapturedrefrace is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Gocapturedrefrace. If not, see
-// .
-
-// TODO: package documentation.
-package gocapturedrefrace
-
-import (
- "go/ast"
- "go/token"
- "go/types"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
-)
-
-var version = "0.0.1"
-
-var Analyzer = &analysis.Analyzer{
- Name: "gocapturedrefrace",
- Doc: "reports captured references in goroutine closures",
- Run: run,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
-}
-
-func run(pass *analysis.Pass) (interface{}, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- nodeFilter := []ast.Node{
- (*ast.GoStmt)(nil),
- }
-
- inspect.Preorder(
- nodeFilter,
- func(node ast.Node) {
- // Find `go` statements.
- goStmt, ok := node.(*ast.GoStmt)
- if !ok {
- return
- }
-
- // Look for a function literal after the `go` statement.
- funcLit, ok := goStmt.Call.Fun.(*ast.FuncLit)
- if !ok {
- return
- }
-
- // Inspect closure argument list.
- for _, arg := range funcLit.Type.Params.List {
- // Report reference arguments.
- _, ok := arg.Type.(*ast.StarExpr)
- if !ok {
- continue
- }
-
- pass.Reportf(
- arg.Pos(),
- "reference %s in goroutine closure",
- arg.Names[0],
- )
- }
-
- checkClosure(pass, funcLit)
- },
- )
-
- return nil, nil
-}
-
-// checkClosure reports variables used in funcLit that are captured from an
-// outer scope.
-func checkClosure(pass *analysis.Pass, funcLit *ast.FuncLit) {
- // Get the closure's scope.
- funcScope := pass.TypesInfo.Scopes[funcLit.Type]
-
- // Build a list of assignments local to funcLit. These will be ignored as
- // shadowed variables.
- localVarDeclarations := findLocalVarDeclarations(funcLit)
-
- ast.Inspect(
- funcLit,
- func(node ast.Node) bool {
- ident, ok := node.(*ast.Ident)
- if !ok {
- return true
- }
-
- if ident.Obj == nil {
- return true
- }
-
- // Ignore shadowed variables.
- for _, declarationIdent := range localVarDeclarations {
- if ident == declarationIdent {
- return true
- }
- }
-
- // Find out whether `ident` was defined in an outer scope.
- scope, scopeObj := funcScope.LookupParent(ident.Name, ident.NamePos)
-
- // Identifier is local to the closure.
- if scope == nil && scopeObj == nil {
- return true
- }
-
- // Ignore non-variable identifiers.
- variable, ok := scopeObj.(*types.Var)
- if !ok {
- return true
- }
-
- // Ignore captured callable variables, like function arguments.
- _, isVariableTypeSignature := variable.Type().(*types.Signature)
- if isVariableTypeSignature {
- return true
- }
-
- // Identifier was defined in a different scope.
- if funcScope != scope {
- pass.Reportf(
- ident.Pos(),
- "captured reference %s in goroutine closure",
- ident,
- )
- }
-
- return true
- },
- )
-}
-
-// findLocalVarDeclarations returns a list of all variable declarations in
-// funcLit.
-func findLocalVarDeclarations(
- funcLit *ast.FuncLit,
-) (declarations []*ast.Ident) {
- declarations = []*ast.Ident{}
-
- ast.Inspect(
- funcLit,
- func(node ast.Node) bool {
- switch node := node.(type) {
- case *ast.AssignStmt:
- assignments := assignmentDefinitions(node)
- declarations = append(declarations, assignments...)
-
- case *ast.GenDecl:
- decls := varDeclarations(node)
- if decls != nil {
- declarations = append(declarations, decls...)
- }
- }
-
- return true
- },
- )
-
- return declarations
-}
-
-// assignmentDefinitions returns the identifiers corresponding to variable
-// assignments in assignStmt, or nil if assignStmt does not declare any
-// variables.
-func assignmentDefinitions(
- assignStmt *ast.AssignStmt,
-) (assignments []*ast.Ident) {
- assignments = []*ast.Ident{}
-
- if assignStmt.Tok != token.DEFINE {
- return nil
- }
-
- for _, lhs := range assignStmt.Lhs {
- ident, ok := lhs.(*ast.Ident)
- if !ok {
- continue
- }
-
- if ident == nil {
- continue
- }
-
- assignments = append(assignments, ident)
- }
-
- return assignments
-}
-
-// varDeclarations returns the identifiers corresponding to variable
-// declarations in decl, or nil if decl is not a variable declaration.
-func varDeclarations(decl *ast.GenDecl) (declarations []*ast.Ident) {
- if decl.Tok != token.VAR {
- return nil
- }
-
- declarations = []*ast.Ident{}
-
- for _, spec := range decl.Specs {
- valueSpec, ok := spec.(*ast.ValueSpec)
- if !ok {
- return declarations
- }
-
- for _, ident := range valueSpec.Names {
- declarations = append(declarations, ident)
- }
- }
-
- return declarations
-}
diff --git a/gocapturedrefrace_test.go b/gocapturedrefrace_test.go
deleted file mode 100644
index fd5fc53..0000000
--- a/gocapturedrefrace_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2023 Teddy Wing
-//
-// This file is part of Gocapturedrefrace.
-//
-// Gocapturedrefrace is free software: you can redistribute it and/or
-// modify it under the terms of the GNU General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// Gocapturedrefrace is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Gocapturedrefrace. If not, see
-// .
-
-
-package gocapturedrefrace_test
-
-import (
- "testing"
-
- "golang.org/x/tools/go/analysis/analysistest"
- "gopkg.teddywing.com/gocapturedrefrace"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
-
- analysistest.Run(t, testdata, gocapturedrefrace.Analyzer, ".")
-}
--
cgit v1.2.3