-
Notifications
You must be signed in to change notification settings - Fork 437
panicinlibrarycode: enforce FuncLit boundaries for init/doc panic exemptions #41631
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,7 +179,10 @@ func isFmtSprintf(pass *analysis.Pass, call *ast.CallExpr) bool { | |
| // function. Only top-level (no receiver) init functions are recognized; | ||
| // methods named init are ordinary methods and are not exempt. | ||
| func isInInitFunction(cur inspector.Cursor) bool { | ||
| for encl := range cur.Enclosing((*ast.FuncDecl)(nil)) { | ||
| for encl := range cur.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) { | ||
| if _, isFuncLit := encl.Node().(*ast.FuncLit); isFuncLit { | ||
| return false | ||
| } | ||
| decl, ok := encl.Node().(*ast.FuncDecl) | ||
| if !ok { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code: 💡 Details and fix
The same issue exists at line 204 in Replace both with a direct assertion that makes the invariant explicit: decl := encl.Node().(*ast.FuncDecl) |
||
| break | ||
|
|
@@ -193,7 +196,10 @@ func isInInitFunction(cur inspector.Cursor) bool { | |
| } | ||
|
|
||
| func hasDocumentedPanicContract(cur inspector.Cursor) bool { | ||
| for encl := range cur.Enclosing((*ast.FuncDecl)(nil)) { | ||
| for encl := range cur.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) { | ||
| if _, isFuncLit := encl.Node().(*ast.FuncLit); isFuncLit { | ||
| return false | ||
| } | ||
| decl, ok := encl.Node().(*ast.FuncDecl) | ||
| if !ok { | ||
| break | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,13 @@ func init() { | |
| panic("startup registration failure") // should not be flagged | ||
| } | ||
|
|
||
| func init() { | ||
| handler := func() { | ||
| panic("handler panic outside init flow") // want `avoid panic in library code; return an error instead` | ||
| } | ||
| _ = handler | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The test covers one level of closure nesting. Consider adding a two-level case to explicitly document that the boundary check handles deep nesting (the algorithm is correct because 💡 Suggested test casefunc init() {
outer := func() {
inner := func() {
panic("deeply nested") // want `avoid panic in library code; return an error instead`
}
_ = inner
}
_ = outer
}Without this, a future change that accidentally breaks inner-to-outer traversal order would not be caught by these tests. |
||
|
|
||
| // ok: panic inside a sync.Once.Do callback. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing test for 💡 Details and suggested testThe FuncLit boundary change makes return isInSyncOnceDoFuncLit(pass, cur) || // must fire first
panicMessageStartsWithBUG(pass, call) ||
isInInitFunction(cur) || // now returns false for any FuncLit boundary
hasDocumentedPanicContract(cur)This ordering contract is undocumented. If the checks are ever reordered, the Add a companion case here: func init() {
once.Do(func() {
panic("lazy init inside init()") // should not be flagged
})
} |
||
| var once sync.Once | ||
|
|
||
|
|
@@ -71,6 +78,15 @@ func documentedPreconditionPanics(mode string) { | |
| } | ||
| } | ||
|
|
||
| // documentedClosureRegistration panics if called with an empty input. | ||
| func documentedClosureRegistration(input string) { | ||
| callback := func() { | ||
| panic("callback panic should be reported") // want `avoid panic in library code; return an error instead` | ||
| } | ||
| _ = callback | ||
| _ = input | ||
| } | ||
|
|
||
| // ok: method named init is NOT a top-level init; its panic should be flagged. | ||
| type myInitType struct{} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/improve-codebase-architecture] The identical 3-line FuncLit boundary guard is duplicated verbatim in
isInInitFunction(lines 183–185) andhasDocumentedPanicContract(lines 200–202). A third exemption function added later would likely miss this invariant.💡 Suggested: extract a shared helper
Both callers become single-expression checks, and the boundary invariant lives in one place.