Skip to content

Go error rules#82

Open
GrosQuildu wants to merge 11 commits into
mainfrom
go-error-rules
Open

Go error rules#82
GrosQuildu wants to merge 11 commits into
mainfrom
go-error-rules

Conversation

@GrosQuildu
Copy link
Copy Markdown
Collaborator

@GrosQuildu GrosQuildu commented May 6, 2026

Adds three rules:

  • shadowed-err-checkif xErr := f(); err != nil declares one err-like var but checks a different one (typo / copy-paste).
  • pkg-errors-wrap-nil-errpkg/errors.Wrap / Wrapf / WithMessage / WithMessagef / WithStack called on a provably-nil err; these return nil for nil input, silently swallowing the failure path.
  • http-error-missing-returnhttp.Error(w, ...) not followed by return; handler keeps running after the error response.

I used regexes for error detection: typed metavariables don't seem to work for this type.

Copy link
Copy Markdown
Member

@mschwager mschwager left a comment

Choose a reason for hiding this comment

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

Nice work. It looks like LLMs create very verbose Semgrep rules, and probably attempt to brute force various scenarios rather than generalize rules. I have had some success by telling LLMs to review existing rules before creating new ones so it can observe some of our more advanced usage like metavariable-pattern.

Semgrep has a lot of implicit equivalences, which are difficult for an LLM to learn. Perhaps we can encode this knowledge somewhere, but for now it will probably require a bit of human review.

Comment on lines +70 to +75
- pattern-inside: |
switch {
case $L1:
...
}
$NEXT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

TIL about switch with missing expression

Comment on lines +48 to +87
- pattern-inside: |
if $C1 {
...
}
$NEXT
- pattern-inside: |
if $I1; $C1 {
...
}
$NEXT
- pattern-inside: |
switch $X1 {
case $L1:
...
}
$NEXT
- pattern-inside: |
switch $I1; $X1 {
case $L1:
...
}
$NEXT
- pattern-inside: |
switch {
case $L1:
...
}
$NEXT
- pattern-inside: |
switch $X1 {
default:
...
}
$NEXT
- pattern-inside: |
switch {
default:
...
}
$NEXT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think these 7 pattern-insides can be reduced to 3 that cover the same cases: https://semgrep.dev/playground/s/Ok7qb

Kinda annoying that switch ... { ... } doesn't catch all switch statements (could probably be raised as an upstream issue). And you need to include a case to make a valid pattern, and it also catches just default switch statements! 😕

Comment on lines +98 to +100
- metavariable-regex:
metavariable: $TERM
regex: '^(return\b|panic\(|log\.Fatal(ln|f)?\(|os\.Exit\(|runtime\.Goexit\(|continue\b|break\b|goto\b)'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this can be better expressed using metavariable-pattern: https://semgrep.dev/playground/s/er9dE

Comment on lines +108 to +147
- pattern: |
if $C2 {
...
}
$TERM
- pattern: |
if $I2; $C2 {
...
}
$TERM
- pattern: |
switch $X2 {
case $L2:
...
}
$TERM
- pattern: |
switch $I2; $X2 {
case $L2:
...
}
$TERM
- pattern: |
switch {
case $L2:
...
}
$TERM
- pattern: |
switch $X2 {
default:
...
}
$TERM
- pattern: |
switch {
default:
...
}
$TERM
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above, this can be 3 statements instead of 7.

Comment on lines +148 to +150
- metavariable-regex:
metavariable: $TERM
regex: '^(return\b|panic\(|log\.Fatal(ln|f)?\(|os\.Exit\(|runtime\.Goexit\(|continue\b|break\b|goto\b)'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above, metavariable-pattern.

Comment on lines +193 to +204
- metavariable-pattern:
metavariable: $RET
patterns:
- pattern-either:
- pattern: return errors.$FN($ERR, ...)
- pattern: $ERR = errors.$FN($ERR, ...)
# `... $RET ...` could bind $RET to any stmt-level node
# (including an enclosing if-stmt containing the wrap).
# Anchor $RET's text to the return/assignment shape.
- metavariable-regex:
metavariable: $RET
regex: '^(return\b|[a-zA-Z_][a-zA-Z0-9_]*\s*=(?!=))'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is this trying to do? So $RET needs to be one of the pattern-eithers above AND this regex? I'm confused. Could we instead include this constraint in the metavariable-pattern above?

Comment on lines +205 to +238
# $ERR reassigned (or `:=`-shadowed) IMMEDIATELY before the
# wrap (return-form) inside the else body. Tight-adjacent only.
- pattern-not: |
if $ERR != nil {
...
} else {
...
$ERR = ...
return errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
..., $ERR = ...
return errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
$ERR := ...
return errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
..., $ERR := ...
return errors.$FN($ERR, ...)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All 4 of these assignment cases should be covered by a single $ERR = ...: https://semgrep.dev/playground/s/Lr7W7

Comment on lines +239 to +271
# Same shape for the assignment-form wrap.
- pattern-not: |
if $ERR != nil {
...
} else {
...
$ERR = ...
$ERR = errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
..., $ERR = ...
$ERR = errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
$ERR := ...
$ERR = errors.$FN($ERR, ...)
}
- pattern-not: |
if $ERR != nil {
...
} else {
...
..., $ERR := ...
$ERR = errors.$FN($ERR, ...)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment on lines +308 to +319
- pattern-not-inside: |
$ERR = ...
return errors.$FN($ERR, ...)
- pattern-not-inside: |
..., $ERR = ...
return errors.$FN($ERR, ...)
- pattern-not-inside: |
$ERR := ...
return errors.$FN($ERR, ...)
- pattern-not-inside: |
..., $ERR := ...
return errors.$FN($ERR, ...)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Comment on lines +320 to +331
- pattern-not-inside: |
$ERR = ...
$ERR = errors.$FN($ERR, ...)
- pattern-not-inside: |
..., $ERR = ...
$ERR = errors.$FN($ERR, ...)
- pattern-not-inside: |
$ERR := ...
$ERR = errors.$FN($ERR, ...)
- pattern-not-inside: |
..., $ERR := ...
$ERR = errors.$FN($ERR, ...)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants