Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static analysis to enforce usage of InitWithReset #5654

Merged
merged 13 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ nogo(
"//tools/analyzers/maligned:go_tool_library",
"//tools/analyzers/roughtime:go_tool_library",
"//tools/analyzers/errcheck:go_tool_library",
"//tools/analyzers/featureconfig:go_tool_library",
],
)

Expand Down
6 changes: 3 additions & 3 deletions beacon-chain/db/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ go_library(
"alias.go",
"http_backup_handler.go",
] + select({
"//conditions:default": [
"db_kafka_wrapped.go",
],
":kafka_disabled": [
"db.go",
],
"//conditions:default": [
"db_kafka_wrapped.go",
],
}),
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/db",
visibility = [
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/db/kafka/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"passthrough.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/db/kafka",
tags = ["manual"],
visibility = ["//beacon-chain/db:__pkg__"],
deps = [
"//beacon-chain/db/filters:go_default_library",
Expand Down
5 changes: 5 additions & 0 deletions nogo_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,10 @@
".*/.*mock\\.go": "Mocks are OK",
".*/testmain\\.go": "Test runner generated code"
}
},
"featureconfig": {
"only_files": {
".*_test\\.go": "Only tests"
}
}
}
3 changes: 2 additions & 1 deletion shared/featureconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ The process for implementing new features using this package is as follows:
cfg := &featureconfig.Flags{
VerifyAttestationSigs: true,
}
featureconfig.Init(cfg)
resetCfg := featureconfig.InitWithReset(cfg)
defer resetCfg()
6. Add the string for the flags that should be running within E2E to E2EValidatorFlags
and E2EBeaconChainFlags.
*/
Expand Down
25 changes: 25 additions & 0 deletions tools/analyzers/featureconfig/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_tool_library")

go_library(
name = "go_default_library",
srcs = ["analyzer.go"],
importpath = "github.com/prysmaticlabs/prysm/tools/analyzers/featureconfig",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis:go_default_library",
"@org_golang_x_tools//go/analysis/passes/inspect:go_default_library",
"@org_golang_x_tools//go/ast/inspector:go_default_library",
],
)

go_tool_library(
name = "go_tool_library",
srcs = ["analyzer.go"],
importpath = "featureconfig",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis:go_tool_library",
"@org_golang_x_tools//go/analysis/passes/inspect:go_tool_library",
"@org_golang_x_tools//go/ast/inspector:go_tool_library",
],
)
91 changes: 91 additions & 0 deletions tools/analyzers/featureconfig/analyzer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package featureconfig

import (
"errors"
"go/ast"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)

// Doc explaining the tool.
const Doc = "Enforce usage of featureconfig.InitWithReset to prevent leaking globals in tests."

// Analyzer runs static analysis.
var Analyzer = &analysis.Analyzer{
Name: "featureconfig",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !ok {
return nil, errors.New("analyzer is not type *inspector.Inspector")
}

nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
(*ast.ExprStmt)(nil),
(*ast.GoStmt)(nil),
(*ast.DeferStmt)(nil),
(*ast.AssignStmt)(nil),
}

inspect.Preorder(nodeFilter, func(node ast.Node) {
if ce, ok := node.(*ast.CallExpr); ok && isPkgDot(ce.Fun, "featureconfig", "Init") {
reportForbiddenUsage(pass, ce.Pos())
return
}
switch stmt := node.(type) {
case *ast.ExprStmt:
if call, ok := stmt.X.(*ast.CallExpr); ok&& isPkgDot(call.Fun, "featureconfig", "InitWithReset"){
reportUnhandledReset(pass, call.Lparen)
}
case *ast.GoStmt:
if isPkgDot(stmt.Call, "featureconfig", "InitWithReset") {
reportUnhandledReset(pass, stmt.Call.Lparen)
}
case *ast.DeferStmt:
if isPkgDot(stmt.Call, "featureconfig", "InitWithReset") {
reportUnhandledReset(pass, stmt.Call.Lparen)
}
case *ast.AssignStmt:
if ce, ok := stmt.Rhs[0].(*ast.CallExpr); ok && isPkgDot(ce.Fun, "featureconfig", "InitWithReset") {
for i := 0; i < len(stmt.Lhs); i++ {
if id, ok := stmt.Lhs[i].(*ast.Ident); ok {
if id.Name == "_" {
reportUnhandledReset(pass, id.NamePos)
}
}
}
}
default:
}
})

return nil, nil
}

func reportForbiddenUsage(pass *analysis.Pass, pos token.Pos) {
pass.Reportf(pos, "Use of featureconfig.Init is forbidden in test code. Please use " +
"featureconfig.InitWithReset and call reset in the same test function.")
}

func reportUnhandledReset(pass *analysis.Pass, pos token.Pos) {
pass.Reportf(pos, "Unhandled reset featureconfig not found in test " +
"method. Be sure to call the returned reset function from featureconfig.InitWithReset " +
"within this test method.")
}

func isPkgDot(expr ast.Expr, pkg, name string) bool {
sel, ok := expr.(*ast.SelectorExpr)
return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name)
}

func isIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
8 changes: 2 additions & 6 deletions tools/extractor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ var (
state = flag.Uint("state", 0, "Extract state at this slot.")
)

func init() {
fc := featureconfig.Get()
fc.WriteSSZStateTransitions = true
featureconfig.Init(fc)
}

func main() {
resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{WriteSSZStateTransitions: true})
defer resetCfg()
flag.Parse()
fmt.Println("Starting process...")
d, err := db.NewDB(*datadir, cache.NewStateSummaryCache())
Expand Down
3 changes: 2 additions & 1 deletion validator/client/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func TestCancelledContext_WaitsForSynced(t *testing.T) {
cfg := &featureconfig.Flags{
WaitForSynced: true,
}
featureconfig.Init(cfg)
reset := featureconfig.InitWithReset(cfg)
defer reset()
v := &fakeValidator{}
run(cancelledContext(), v)
if !v.WaitForSyncedCalled {
Expand Down
9 changes: 6 additions & 3 deletions validator/client/validator_attest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ func TestAttestToBlockHead_BlocksDoubleAtt(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: true,
}
featureconfig.Init(config)
reset := featureconfig.InitWithReset(config)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down Expand Up @@ -173,7 +174,8 @@ func TestAttestToBlockHead_BlocksSurroundAtt(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: true,
}
featureconfig.Init(config)
reset := featureconfig.InitWithReset(config)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down Expand Up @@ -224,7 +226,8 @@ func TestAttestToBlockHead_BlocksSurroundedAtt(t *testing.T) {
config := &featureconfig.Flags{
ProtectAttester: true,
}
featureconfig.Init(config)
reset := featureconfig.InitWithReset(config)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down
22 changes: 13 additions & 9 deletions validator/client/validator_propose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func setup(t *testing.T) (*validator, *mocks, func()) {
}

validator := &validator{
db: valDB,
validatorClient: m.validatorClient,
keyManager: testKeyManager,
graffiti: []byte{},
attLogs: make(map[[32]byte]*attSubmitted),
db: valDB,
validatorClient: m.validatorClient,
keyManager: testKeyManager,
graffiti: []byte{},
attLogs: make(map[[32]byte]*attSubmitted),
aggregatedSlotCommitteeIDCache: aggregatedSlotCommitteeIDCache,
}

Expand Down Expand Up @@ -119,7 +119,8 @@ func TestProposeBlock_BlocksDoubleProposal(t *testing.T) {
cfg := &featureconfig.Flags{
ProtectProposer: true,
}
featureconfig.Init(cfg)
reset := featureconfig.InitWithReset(cfg)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down Expand Up @@ -156,7 +157,8 @@ func TestProposeBlock_BlocksDoubleProposal_After54KEpochs(t *testing.T) {
cfg := &featureconfig.Flags{
ProtectProposer: true,
}
featureconfig.Init(cfg)
reset := featureconfig.InitWithReset(cfg)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down Expand Up @@ -194,7 +196,8 @@ func TestProposeBlock_AllowsPastProposals(t *testing.T) {
cfg := &featureconfig.Flags{
ProtectProposer: true,
}
featureconfig.Init(cfg)
reset := featureconfig.InitWithReset(cfg)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down Expand Up @@ -233,7 +236,8 @@ func TestProposeBlock_AllowsSameEpoch(t *testing.T) {
cfg := &featureconfig.Flags{
ProtectProposer: true,
}
featureconfig.Init(cfg)
reset := featureconfig.InitWithReset(cfg)
defer reset()
hook := logTest.NewGlobal()
validator, m, finish := setup(t)
defer finish()
Expand Down