Files
gocp/tool_find_panic_recover.go

175 lines
4.7 KiB
Go
Raw Normal View History

package main
import (
"go/ast"
"go/token"
)
type PanicRecoverUsage struct {
Type string `json:"type"` // "panic" or "recover"
Position Position `json:"position"`
InDefer bool `json:"in_defer"`
Message string `json:"message,omitempty"`
Context string `json:"context"`
}
type PanicRecoverAnalysis struct {
Usages []PanicRecoverUsage `json:"usages"`
Issues []PanicRecoverIssue `json:"issues"`
}
type PanicRecoverIssue struct {
Type string `json:"type"`
Description string `json:"description"`
Position Position `json:"position"`
}
func findPanicRecover(dir string) (*PanicRecoverAnalysis, error) {
analysis := &PanicRecoverAnalysis{
Usages: []PanicRecoverUsage{},
Issues: []PanicRecoverIssue{},
}
err := walkGoFiles(dir, func(path string, src []byte, file *ast.File, fset *token.FileSet) error {
// Track function boundaries and defer statements
var currentFunc *ast.FuncDecl
deferDepth := 0
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
currentFunc = node
deferDepth = 0
case *ast.DeferStmt:
deferDepth++
// Check for recover in defer
ast.Inspect(node, func(inner ast.Node) bool {
if call, ok := inner.(*ast.CallExpr); ok {
if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "recover" {
pos := fset.Position(call.Pos())
usage := PanicRecoverUsage{
Type: "recover",
Position: newPosition(pos),
InDefer: true,
Context: extractContext(src, pos),
}
analysis.Usages = append(analysis.Usages, usage)
}
}
return true
})
deferDepth--
case *ast.CallExpr:
if ident, ok := node.Fun.(*ast.Ident); ok {
pos := fset.Position(node.Pos())
switch ident.Name {
case "panic":
message := extractPanicMessage(node)
usage := PanicRecoverUsage{
Type: "panic",
Position: newPosition(pos),
InDefer: deferDepth > 0,
Message: message,
Context: extractContext(src, pos),
}
analysis.Usages = append(analysis.Usages, usage)
// Check if panic is in main or init
if currentFunc != nil && (currentFunc.Name.Name == "main" || currentFunc.Name.Name == "init") {
issue := PanicRecoverIssue{
Type: "panic_in_main_init",
Description: "Panic in " + currentFunc.Name.Name + " function will crash the program",
Position: newPosition(pos),
}
analysis.Issues = append(analysis.Issues, issue)
}
case "recover":
if deferDepth == 0 {
issue := PanicRecoverIssue{
Type: "recover_outside_defer",
Description: "recover() called outside defer statement - it will always return nil",
Position: newPosition(pos),
}
analysis.Issues = append(analysis.Issues, issue)
}
usage := PanicRecoverUsage{
Type: "recover",
Position: newPosition(pos),
InDefer: deferDepth > 0,
Context: extractContext(src, pos),
}
analysis.Usages = append(analysis.Usages, usage)
}
}
}
return true
})
// Check for functions with panic but no recover
checkPanicWithoutRecover(file, fset, analysis)
return nil
})
return analysis, err
}
func extractPanicMessage(call *ast.CallExpr) string {
if len(call.Args) > 0 {
switch arg := call.Args[0].(type) {
case *ast.BasicLit:
return arg.Value
case *ast.Ident:
return arg.Name
case *ast.SelectorExpr:
return exprToString(arg)
default:
return "complex expression"
}
}
return ""
}
func checkPanicWithoutRecover(file *ast.File, fset *token.FileSet, analysis *PanicRecoverAnalysis) {
// For each function, check if it has panic but no recover
ast.Inspect(file, func(n ast.Node) bool {
funcDecl, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
hasPanic := false
hasRecover := false
var panicPos token.Position
ast.Inspect(funcDecl, func(inner ast.Node) bool {
if call, ok := inner.(*ast.CallExpr); ok {
if ident, ok := call.Fun.(*ast.Ident); ok {
if ident.Name == "panic" {
hasPanic = true
panicPos = fset.Position(call.Pos())
} else if ident.Name == "recover" {
hasRecover = true
}
}
}
return true
})
if hasPanic && !hasRecover && funcDecl.Name.Name != "main" && funcDecl.Name.Name != "init" {
issue := PanicRecoverIssue{
Type: "panic_without_recover",
Description: "Function " + funcDecl.Name.Name + " calls panic() but has no recover() - consider adding error handling",
Position: newPosition(panicPos),
}
analysis.Issues = append(analysis.Issues, issue)
}
return true
})
}