Skip to content

Commit

Permalink
Add static analysis to enforce usage of InitWithReset (#5654)
Browse files Browse the repository at this point in the history
* Refactor attestation packing slightly to reduce the skip slot / HTR of process slots
* Merge branch 'master' into refactor-attestation-packing
* gofmt
* Merge branch 'refactor-attestation-packing' of github.com:prysmaticlabs/prysm into refactor-attestation-packing
* Merge branch 'master' of github.com:prysmaticlabs/prysm into refactor-attestation-packing
* Add static analysis to enforce usage of InitWithReset
* Add comment / lint
* fix a few usages
* more fixes for featureconfig.Init
* Fix analyzer
* Merge branch 'sa-fc-init' of github.com:prysmaticlabs/prysm into sa-fc-init
* Merge refs/heads/master into sa-fc-init
* Merge refs/heads/master into sa-fc-init
  • Loading branch information
prestonvanloon authored Apr 28, 2020
1 parent 6700383 commit e30349d
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 23 deletions.
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

0 comments on commit e30349d

Please sign in to comment.