errortype
LinterI've been investigating a class of subtle bugs in Go error handling related to pointer vs. value semantics with errors.As
, and developed a static analysis tool to detect these issues. I'm seeking feedback from the community on both the approach and the tool's effectiveness.
Consider this code attempting to handle AES key size errors:
key := []byte("My kung fu is better than yours")
_, err := aes.NewCipher(key)
var kse *aes.KeySizeError
if errors.As(err, &kse) {
fmt.Printf("AES keys must be 16, 24 or 32 bytes long, got %d bytes.\n", kse)
} else if err != nil {
fmt.Println(err)
}
The bug: aes.NewCipher
returns aes.KeySizeError
as a value, but the code checks for a pointer to *aes.KeySizeError
. This type mismatch causes errors.As
to fail silently, with no compile-time detection.
The core issue is that Go's error interface allows both pointer and value types to implement error
, but the dynamic type matching in errors.As
requires exact type correspondence. This creates opportunities for silent failures when the expected and actual types don't align.
I propose a two-part mitigation strategy:
Explicit compile-time assertions to document intended usage patterns:
// MyValueError is intended to be used as a value
var _ error = MyValueError{}
// MyPointerError is intended to be used as a pointer
var _ error = (*MyPointerError)(nil)
I've developed errortype
, a static analyzer that detects inconsistencies between intended error type usage and actual usage patterns. The tool analyzes:
errors.As
target parametersExample diagnostic output:
main.go:14:20: Target for value error "crypto/aes.KeySizeError" is a pointer-to-pointer, use a pointer to a value instead: "var kse aes.KeySizeError; ... errors.As(err, &kse)". (et:err)
I'm particularly interested in feedback on a few points:
Prevalence: Have you encountered this pointer/value ambiguity with error types? How common do you think this class of bug is in practice?
Solution Approach: What are your thoughts on using var _ error = ...
assertions to declare an error type's intended usage? Is this a practical convention to adopt?
Tooling: The detection heuristics are based on these assertions and usage pattern analysis. I would be grateful for any real-world testing on your codebases to validate their effectiveness and performance.
The tool is currently CLI-only while I refine the detection logic based on real-world usage patterns. Integration is planned in a later phase.
I welcome any insights, particularly from those who have encountered similar issues or have thoughts on static analysis approaches for Go error handling patterns.
Best regards, Oliver
Static Analysis for Go Error Type Consistency:
errortype
LinterI've been investigating a class of subtle bugs in Go error handling related to pointer vs. value semantics with
errors.As
, and developed a static analysis tool to detect these issues. I'm seeking feedback from the community on both the approach and the tool's effectiveness.Problem Statement
Consider this code attempting to handle AES key size errors:
key := []byte("My kung fu is better than yours") _, err := aes.NewCipher(key) var kse *aes.KeySizeError if errors.As(err, &kse) { fmt.Printf("AES keys must be 16, 24 or 32 bytes long, got %d bytes.\n", kse) } else if err != nil { fmt.Println(err) }
The bug:
aes.NewCipher
returnsaes.KeySizeError
as a value, but the code checks for a pointer to*aes.KeySizeError
. This type mismatch causeserrors.As
to fail silently, with no compile-time detection.Analysis and Solution Approach
The core issue is that Go's error interface allows both pointer and value types to implement
error
, but the dynamic type matching inerrors.As
requires exact type correspondence. This creates opportunities for silent failures when the expected and actual types don't align.
I propose a two-part mitigation strategy:
1. Compile-time Intent Declaration
Explicit compile-time assertions to document intended usage patterns:
// MyValueError is intended to be used as a value var _ error = MyValueError{} // MyPointerError is intended to be used as a pointer var _ error = (*MyPointerError)(nil)
2. Static Analysis Tool
I've developed
errortype
, a static analyzer that detects inconsistencies between intended error type usage and actual usage patterns. The tool analyzes:
- Function return value types
- Type assertions and type switches
errors.As
target parameters- Method receiver consistency patterns
Example diagnostic output:
main.go:14:20: Target for value error "crypto/aes.KeySizeError" is a pointer-to-pointer, use a pointer to a value instead: "var kse aes.KeySizeError; ... errors.As(err, &kse)". (et:err)
Request for Feedback
I'm particularly interested in feedback on a few points:
Prevalence: Have you encountered this pointer/value ambiguity with error types? How common do you think this class of bug is in practice?
Solution Approach: What are your thoughts on using
var _ error = ...
assertions to declare an error type's intended usage? Is this a practical convention to adopt?Tooling: The detection heuristics are based on these assertions and usage pattern analysis. I would be grateful for any real-world testing on your codebases to validate their effectiveness and performance.
The tool is currently CLI-only while I refine the detection logic based on real-world usage patterns. Integration is planned in a later phase.
References
- Detailed analysis: https://blog.fillmore-labs.com/posts/errors-1/
- Tool repository: https://github.com/fillmore-labs/errortype
- Playground example: https://go.dev/play/p/m4SEPqkZ2Zu
I welcome any insights, particularly from those who have encountered similar issues or have thoughts on static analysis approaches for Go error handling patterns.
Best regards, Oliver
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/0AF06538-3EAB-4E89-8F9A-25B7002237B8%40fillmore-labs.com.
Am 19.08.2025 um 00:22 schrieb Kevin Chowski <Ke...@chowski.com>:Regarding the "var _ error" bit - why not just look at the Error method to see whether it is on a T or a *T? I haven't done a survey about it or anything, but if I need to know whether to pass a T or a *T I often check that type's methods.
Am 19.08.2025 um 06:57 schrieb Axel Wagner <axel.wa...@googlemail.com>:Hi,On Fri, 15 Aug 2025 at 13:10, Oliver Eikemeier <eike...@fillmore-labs.com> wrote:Static Analysis for Go Error Type Consistency:
errortype
LinterI've been investigating a class of subtle bugs in Go error handling related to pointer vs. value semantics with
errors.As
, and developed a static analysis tool to detect these issues. I'm seeking feedback from the community on both the approach and the tool's effectiveness.Problem Statement
Consider this code attempting to handle AES key size errors:
key := []byte("My kung fu is better than yours") _, err := aes.NewCipher(key) var kse *aes.KeySizeError if errors.As(err, &kse) { fmt.Printf("AES keys must be 16, 24 or 32 bytes long, got %d bytes.\n", kse) } else if err != nil { fmt.Println(err) }
The bug:
aes.NewCipher
returnsaes.KeySizeError
as a value, but the code checks for a pointer to*aes.KeySizeError
. This type mismatch causeserrors.As
to fail silently, with no compile-time detection.Analysis and Solution Approach
The core issue is that Go's error interface allows both pointer and value types to implement
error
, but the dynamic type matching inerrors.As
requires exact type correspondence. This creates opportunities for silent failures when the expected and actual types don't align.That is the reason why it is generally recommended that error types use a pointer receiver. That statically rules out mistakes like this. It is unfortunate, that crypto/aes did not follow this recommendation.
I propose a two-part mitigation strategy:
1. Compile-time Intent Declaration
Explicit compile-time assertions to document intended usage patterns:
// MyValueError is intended to be used as a value var _ error = MyValueError{} // MyPointerError is intended to be used as a pointer var _ error = (*MyPointerError)(nil)
I think if someone does this, they are aware of the problem and should just make their Error methods a pointer receiver.
In my opinion, that is a much better recommendation, as it means this mistake can't happen to users of a package, even if they don't use your linter.
errortype
promotes a consistent style, a broader refactor of your error handling strategy may sometimes be the better solution.”I know that you disagree with the blanket "all error receivers should be pointers" advice, though.
2. Static Analysis Tool
I've developed
errortype
, a static analyzer that detects inconsistencies between intended error type usage and actual usage patterns. The tool analyzes:
- Function return value types
- Type assertions and type switches
errors.As
target parameters- Method receiver consistency patterns
For clarity: I understand your linter to1. Check that a package declaring SomeError uses that consistently - that is, either exclusively assigns SomeError or exclusively assigns *SomeError to error. It complains, if not.
2. If that usage is consistent, checks that any users of that package *also* only assign the type consistent with that. It complains, if not.
3. Checks that all type-assertions/-switches and errors.As/errors.Is calls happen consistent with that (if it is unambiguous)
In particular, AIUI you do not rely on the assignment-statements above to work, right?
That seems like a very useful tool.
Example diagnostic output:
main.go:14:20: Target for value error "crypto/aes.KeySizeError" is a pointer-to-pointer, use a pointer to a value instead: "var kse aes.KeySizeError; ... errors.As(err, &kse)". (et:err)
Request for Feedback
I'm particularly interested in feedback on a few points:
Prevalence: Have you encountered this pointer/value ambiguity with error types? How common do you think this class of bug is in practice? Solution Approach: What are your thoughts on usingvar _ error = ...
assertions to declare an error type's intended usage? Is this a practical convention to adopt? Tooling: The detection heuristics are based on these assertions and usage pattern analysis. I would be grateful for any real-world testing on your codebases to validate their effectiveness and performance.The tool is currently CLI-only while I refine the detection logic based on real-world usage patterns. Integration is planned in a later phase.
References
- Detailed analysis: https://blog.fillmore-labs.com/posts/errors-1/
- Tool repository: https://github.com/fillmore-labs/errortype
- Playground example: https://go.dev/play/p/m4SEPqkZ2Zu
I welcome any insights, particularly from those who have encountered similar issues or have thoughts on static analysis approaches for Go error handling patterns.
Best regards, Oliver