Skip to content

Add static analyzer to discourage use of panic() #15075

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

Merged
merged 9 commits into from
Mar 19, 2025
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ nogo(
"//tools/analyzers/logruswitherror:go_default_library",
"//tools/analyzers/maligned:go_default_library",
"//tools/analyzers/nop:go_default_library",
"//tools/analyzers/nopanic:go_default_library",
"//tools/analyzers/properpermissions:go_default_library",
"//tools/analyzers/recursivelock:go_default_library",
"//tools/analyzers/shadowpredecl:go_default_library",
Expand Down
1 change: 1 addition & 0 deletions api/client/beacon/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"doc.go",
"health.go",
"log.go",
"template.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon",
visibility = ["//visibility:public"],
Expand Down
22 changes: 0 additions & 22 deletions api/client/beacon/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"regexp"
"sort"
"strconv"
"text/template"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
Expand Down Expand Up @@ -64,23 +63,6 @@ func IdFromSlot(s primitives.Slot) StateOrBlockId {
return StateOrBlockId(strconv.FormatUint(uint64(s), 10))
}

// idTemplate is used to create template functions that can interpolate StateOrBlockId values.
func idTemplate(ts string) func(StateOrBlockId) string {
t := template.Must(template.New("").Parse(ts))
f := func(id StateOrBlockId) string {
b := bytes.NewBuffer(nil)
err := t.Execute(b, struct{ Id string }{Id: string(id)})
if err != nil {
panic(fmt.Sprintf("invalid idTemplate: %s", ts))
}
return b.String()
}
// run the template to ensure that it is valid
// this should happen load time (using package scoped vars) to ensure runtime errors aren't possible
_ = f(IdGenesis)
return f
}

// RenderGetBlockPath formats a block id into a path for the GetBlock API endpoint.
func RenderGetBlockPath(id StateOrBlockId) string {
return path.Join(getSignedBlockPath, string(id))
Expand Down Expand Up @@ -114,8 +96,6 @@ func (c *Client) GetBlock(ctx context.Context, blockId StateOrBlockId) ([]byte,
return b, nil
}

var getBlockRootTpl = idTemplate(getBlockRootPath)

// GetBlockRoot retrieves the hash_tree_root of the BeaconBlock for the given block id.
// Block identifier can be one of: "head" (canonical head in node's view), "genesis", "finalized",
// <slot>, <hex encoded blockRoot with 0x prefix>. Variables of type StateOrBlockId are exported by this package
Expand All @@ -138,8 +118,6 @@ func (c *Client) GetBlockRoot(ctx context.Context, blockId StateOrBlockId) ([32]
return bytesutil.ToBytes32(rs), nil
}

var getForkTpl = idTemplate(getForkForStatePath)

// GetFork queries the Beacon Node API for the Fork from the state identified by stateId.
// Block identifier can be one of: "head" (canonical head in node's view), "genesis", "finalized",
// <slot>, <hex encoded blockRoot with 0x prefix>. Variables of type StateOrBlockId are exported by this package
Expand Down
34 changes: 34 additions & 0 deletions api/client/beacon/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package beacon

import (
"bytes"
"fmt"
"text/template"
)

type templateFn func(StateOrBlockId) string

var getBlockRootTpl templateFn
var getForkTpl templateFn

func init() {
// idTemplate is used to create template functions that can interpolate StateOrBlockId values.
idTemplate := func(ts string) func(StateOrBlockId) string {
t := template.Must(template.New("").Parse(ts))
f := func(id StateOrBlockId) string {
b := bytes.NewBuffer(nil)
err := t.Execute(b, struct{ Id string }{Id: string(id)})
if err != nil {
panic(fmt.Sprintf("invalid idTemplate: %s", ts))
}
return b.String()
}
// run the template to ensure that it is valid
// this should happen load time (using package scoped vars) to ensure runtime errors aren't possible
_ = f(IdGenesis)
return f
}

getBlockRootTpl = idTemplate(getBlockRootPath)
getForkTpl = idTemplate(getForkForStatePath)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, ty

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a review comment warranting a new commit, just a note for later - a couple times I've put func init in its own file called init.go and that pattern has grown on me.

2 changes: 1 addition & 1 deletion async/event/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ retry:
continue retry
}
if sub == nil {
panic("event: ResubscribeFunc returned nil subscription and no error")
panic("event: ResubscribeFunc returned nil subscription and no error") // lint:nopanic -- This should never happen.
}
return sub
case <-s.unsub:
Expand Down
6 changes: 3 additions & 3 deletions beacon-chain/db/kv/archived_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (s *Store) LastArchivedRoot(ctx context.Context) [32]byte {
_, blockRoot = bkt.Cursor().Last()
return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}

return bytesutil.ToBytes32(blockRoot)
Expand All @@ -53,7 +53,7 @@ func (s *Store) ArchivedPointRoot(ctx context.Context, slot primitives.Slot) [32
blockRoot = bucket.Get(bytesutil.SlotToBytesBigEndian(slot))
return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}

return bytesutil.ToBytes32(blockRoot)
Expand All @@ -69,7 +69,7 @@ func (s *Store) HasArchivedPoint(ctx context.Context, slot primitives.Slot) bool
exists = iBucket.Get(bytesutil.SlotToBytesBigEndian(slot)) != nil
return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}
return exists
}
2 changes: 1 addition & 1 deletion beacon-chain/db/kv/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (s *Store) HasBlock(ctx context.Context, blockRoot [32]byte) bool {
exists = bkt.Get(blockRoot[:]) != nil
return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}
return exists
}
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/db/kv/deposit_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (s *Store) DepositContractAddress(ctx context.Context) ([]byte, error) {
addr = chainInfo.Get(depositContractAddressKey)
return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}
return addr, nil
}
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/db/kv/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (s *Store) HasState(ctx context.Context, blockRoot [32]byte) bool {
return nil
})
if err != nil {
panic(err)
panic(err) // lint:nopanic -- View never returns an error.
}
return hasState
}
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/execution/block_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func trim(queue *cache.FIFO, maxSize uint64) {
for s := uint64(len(queue.ListKeys())); s > maxSize; s-- {
// #nosec G104 popProcessNoopFunc never returns an error
if _, err := queue.Pop(popProcessNoopFunc); err != nil { // This never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- popProcessNoopFunc never returns an error.
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/execution/testing/mock_faulty_powchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (*FaultyExecutionChain) ChainStartEth1Data() *ethpb.Eth1Data {
func (*FaultyExecutionChain) PreGenesisState() state.BeaconState {
s, err := state_native.InitializeFromProtoUnsafePhase0(&ethpb.BeaconState{})
if err != nil {
panic("could not initialize state")
panic("could not initialize state") // lint:nopanic -- test code.
}
return s
}
Expand Down
10 changes: 5 additions & 5 deletions beacon-chain/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ func (b *BeaconNode) Start() {
log.WithField("times", i-1).Info("Already shutting down, interrupt more to panic")
}
}
panic("Panic closing the beacon node")
panic("Panic closing the beacon node") // lint:nopanic -- Panic is requested by user.
}()

// Wait for stop channel to be closed.
Expand Down Expand Up @@ -706,15 +706,15 @@ func (b *BeaconNode) registerP2P(cliCtx *cli.Context) error {
func (b *BeaconNode) fetchP2P() p2p.P2P {
var p *p2p.Service
if err := b.services.FetchService(&p); err != nil {
panic(err)
panic(err) // lint:nopanic -- This could panic application start if the services are misconfigured.
}
return p
}

func (b *BeaconNode) fetchBuilderService() *builder.Service {
var s *builder.Service
if err := b.services.FetchService(&s); err != nil {
panic(err)
panic(err) // lint:nopanic -- This could panic application start if the services are misconfigured.
}
return s
}
Expand Down Expand Up @@ -1018,13 +1018,13 @@ func (b *BeaconNode) registerPrometheusService(_ *cli.Context) error {
var additionalHandlers []prometheus.Handler
var p *p2p.Service
if err := b.services.FetchService(&p); err != nil {
panic(err)
panic(err) // lint:nopanic -- This could panic application start if the services are misconfigured.
}
additionalHandlers = append(additionalHandlers, prometheus.Handler{Path: "/p2p", Handler: p.InfoHandler})

var c *blockchain.Service
if err := b.services.FetchService(&c); err != nil {
panic(err)
panic(err) // lint:nopanic -- This could panic application start if the services are misconfigured.
}

service := prometheus.NewService(
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/operations/attestations/mock/mock.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// lint:nopanic -- Mock / test code, panic is allowed.
package mock

import (
Expand Down
4 changes: 2 additions & 2 deletions beacon-chain/operations/blstoexec/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ func (m *PoolMock) InsertBLSToExecChange(change *eth.SignedBLSToExecutionChange)

// MarkIncluded --
func (*PoolMock) MarkIncluded(_ *eth.SignedBLSToExecutionChange) {
panic("implement me")
panic("implement me") // lint:nopanic -- mock / test code.
}

// ValidatorExists --
func (*PoolMock) ValidatorExists(_ primitives.ValidatorIndex) bool {
panic("implement me")
panic("implement me") // lint:nopanic -- mock / test code.
}
4 changes: 2 additions & 2 deletions beacon-chain/operations/slashings/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ func (*PoolMock) ConvertToElectra() {}

// MarkIncludedAttesterSlashing --
func (*PoolMock) MarkIncludedAttesterSlashing(_ ethpb.AttSlashing) {
panic("implement me")
panic("implement me") // lint:nopanic -- Test / mock code.
}

// MarkIncludedProposerSlashing --
func (*PoolMock) MarkIncludedProposerSlashing(_ *ethpb.ProposerSlashing) {
panic("implement me")
panic("implement me") // lint:nopanic -- Test / mock code.
}
2 changes: 1 addition & 1 deletion beacon-chain/operations/voluntaryexits/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ func (m *PoolMock) InsertVoluntaryExit(exit *eth.SignedVoluntaryExit) {

// MarkIncluded --
func (*PoolMock) MarkIncluded(_ *eth.SignedVoluntaryExit) {
panic("implement me")
panic("implement me") // lint:nopanic -- Mock / test code.
}
2 changes: 1 addition & 1 deletion beacon-chain/rpc/testutil/mock_blocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ func (m *MockBlocker) Block(_ context.Context, b []byte) (interfaces.ReadOnlySig

// Blobs --
func (m *MockBlocker) Blobs(_ context.Context, _ string, _ []uint64) ([]*blocks.VerifiedROBlob, *core.RpcError) {
panic("implement me")
panic("implement me") // lint:nopanic -- Test code.
}
2 changes: 1 addition & 1 deletion beacon-chain/state/stategen/epoch_boundary_state_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (e *epochBoundaryState) delete(blockRoot [32]byte) error {
func trim(queue *cache.FIFO, maxSize uint64) {
for s := uint64(len(queue.ListKeys())); s > maxSize; s-- {
if _, err := queue.Pop(popProcessNoopFunc); err != nil { // This never returns an error, but we'll handle anyway for sanity.
panic(err)
panic(err) // lint:nopanic -- Never returns an error.
}
}
}
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/state/stategen/mock/mock.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// lint:nopanic -- Mock code, OK to panic.
package mock

import (
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/sync/initial-sync/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (s *Service) Start() {
if errors.Is(s.ctx.Err(), context.Canceled) {
return
}
panic(err)
panic(err) // lint:nopanic -- Unexpected error. This should probably be surfaced with a returned error.
}
log.WithField("slot", s.cfg.Chain.HeadSlot()).Info("Synced up to")
s.markSynced()
Expand Down
8 changes: 4 additions & 4 deletions beacon-chain/sync/subscriber.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ func (s *Service) subscribe(topic string, validator wrappedVal, handle subHandle
_, e, err := forks.RetrieveForkDataFromDigest(digest, genRoot[:])
if err != nil {
// Impossible condition as it would mean digest does not exist.
panic(err)
panic(err) // lint:nopanic -- Impossible condition.
}
base := p2p.GossipTopicMappings(topic, e)
if base == nil {
// Impossible condition as it would mean topic does not exist.
panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topic))
panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topic)) // lint:nopanic -- Impossible condition.
}
return s.subscribeWithBase(s.addDigestToTopic(topic, digest), validator, handle)
}
Expand Down Expand Up @@ -497,13 +497,13 @@ func (s *Service) subscribeWithParameters(
// Retrieve the epoch of the fork corresponding to the digest.
_, epoch, err := forks.RetrieveForkDataFromDigest(digest, genesisValidatorsRoot[:])
if err != nil {
panic(err)
panic(err) // lint:nopanic -- Impossible condition.
}

// Retrieve the base protobuf message.
base := p2p.GossipTopicMappings(topicFormat, epoch)
if base == nil {
panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topicFormat))
panic(fmt.Sprintf("%s is not mapped to any message in GossipTopicMappings", topicFormat)) // lint:nopanic -- Impossible condition.
}

// Retrieve the genesis time.
Expand Down
1 change: 1 addition & 0 deletions build/bazel/bazel.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func NewTmpDir(prefix string) (string, error) {
// `@go_sdk//:files` in its `data` -- this will make sure the whole toolchain
// gets pulled into the sandbox as well. Generally, this function should only
// be called in init().
// lint:nopanic -- This method is only used for testing.
func SetGoEnv() {
gobin, err := Runfile("bin/go")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cache/lru/lru_wrpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func New(size int) *lru.Cache {
cache, err := lru.New(size)
if err != nil {
panic(fmt.Errorf("lru new failed: %w", err))
panic(fmt.Errorf("lru new failed: %w", err)) // lint:nopanic -- This should never panic.
}
return cache
}
Expand All @@ -20,7 +20,7 @@ func New(size int) *lru.Cache {
func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) *lru.Cache {
cache, err := lru.NewWithEvict(size, onEvicted)
if err != nil {
panic(fmt.Errorf("lru new with evict failed: %w", err))
panic(fmt.Errorf("lru new with evict failed: %w", err)) // lint:nopanic -- This should never panic.
}
return cache
}
3 changes: 3 additions & 0 deletions changelog/pvl_nopanic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Added a static analyzer to discourage use of panic() in Prysm
4 changes: 2 additions & 2 deletions cmd/beacon-chain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func before(ctx *cli.Context) error {
f := joonix.NewFormatter()

if err := joonix.DisableTimestampFormat(f); err != nil {
panic(err)
panic(err) // lint:nopanic -- This shouldn't happen, but crashing immediately at startup is OK.
}

logrus.SetFormatter(f)
Expand Down Expand Up @@ -250,7 +250,7 @@ func main() {
defer func() {
if x := recover(); x != nil {
log.Errorf("Runtime panic: %v\n%v", x, string(runtimeDebug.Stack()))
panic(x)
panic(x) // lint:nopanic -- This is just resurfacing the original panic.
}
}()

Expand Down
4 changes: 2 additions & 2 deletions cmd/client-stats/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func main() {
case "fluentd":
f := joonix.NewFormatter()
if err := joonix.DisableTimestampFormat(f); err != nil {
panic(err)
panic(err) // lint:nopanic -- This shouldn't happen, but crashing immediately at startup is OK.
}
logrus.SetFormatter(f)
case "json":
Expand All @@ -94,7 +94,7 @@ func main() {
defer func() {
if x := recover(); x != nil {
log.Errorf("Runtime panic: %v\n%v", x, string(runtimeDebug.Stack()))
panic(x)
panic(x) // lint:nopanic -- This is just resurfacing the original panic.
}
}()

Expand Down
Loading
Loading