Skip to content
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

cmd/vet: warn when errors.As target has type *error #47528

Open
andig opened this issue Aug 4, 2021 · 20 comments
Open

cmd/vet: warn when errors.As target has type *error #47528

andig opened this issue Aug 4, 2021 · 20 comments

Comments

@andig
Copy link
Contributor

@andig andig commented Aug 4, 2021

What version of Go are you using (go version)?

$ go version
go version go1.16.6 darwin/arm64

Does this issue reproduce with the latest release?

yes

What did you do?

Wrote this code https://play.golang.org/p/16cU0kc8Lku and wondered why the errors matched.

var Err = errors.New("sentinel")
err := errors.New("foo")
if errors.As(err, &Err) {
  fmt.Println("why?")
}

Discussion in https://groups.google.com/g/golang-nuts/c/MaYJy_IRbYA/m/uCHV6P87EQAJ?utm_medium=email&utm_source=footer

What did you expect to see?

Of course the code above is wrong, yet I didn't spot it. It would be nice if this kind of programming error could be detected:

  • add a govet check that flags using plain errors with errors.As or
  • @Merovius suggested errors.errorString should implement As() and return false, unless the pointers match.

I'm unsure if the latter is possible. It would strictly break BC (and the function's description). On the other hand ist should only change behaviour in cases that seem invalid from the start?

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

I'm unsure if the latter is possible. It would strictly break BC (and the function's description).

I don't think it break the functions description. It is the purview of any error type to implement As() the way it sees fit.

It does change the behavior of errors.New, but I don't think there is any API contract about the returned error implementing or not implementing other methods than Error. So, I would argue it changes behavior, but it doesn't break compatibility - if a user relies on the current behavior, they rely on undocumented implementation details.

Lastly, even if it was breaking compatibility, there is a clear exception in the Go 1 compatibility contract for fixing bugs. It is the clear intent of errors.New to return unique sentinel errors. The current behavior of errors.As when used with such an error is clearly a bug.

We can still have a vet check to warn about it, but I think the bug should be fixed either way.

Loading

@andig
Copy link
Contributor Author

@andig andig commented Aug 4, 2021

I don't think it break the functions description.

I was referring to:

An error matches target if the error's concrete value is assignable to the value pointed to by target, or ...

The value here is assignable as types match. Having sentinel errors assignable in turn is more because we can't have const errors in Go...

Loading

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

But the complete paragraph is

An error matches target if the error's concrete value is assignable to the value pointed to by target, or if the error has a method As(interface{}) bool such that As(target) returns true. In the latter case, the As method is responsible for setting target.

The existence of the As optional interface is specifically for cases like this, so that error types can overwrite the pure "assignability" behavior.

Loading

@seankhliao seankhliao changed the title detect/prevent invalid uses of errors.As with plain sentinel errors proposal: errors: implement As for errorString Aug 4, 2021
@gopherbot gopherbot added this to the Proposal milestone Aug 4, 2021
@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

Well, this is surprising behavior to me. I would've expected As to be tried first, but it seems it's only used as a fallback.
With errors.As as it is right now, implementing As actually doesn't do anything in this case. I wonder why it is written that way.

Loading

@andig
Copy link
Contributor Author

@andig andig commented Aug 4, 2021

That does indeed seem to contradict the wording of errors.As.

Loading

@ianlancetaylor ianlancetaylor added this to Incoming in Proposals Aug 4, 2021
@bcmills
Copy link
Member

@bcmills bcmills commented Aug 4, 2021

Perhaps cmd/vet should be changed to warn when the second argument to errors.As is statically known to have type *error?

That is pretty much guaranteed to indicate a bug, because it is trivially true that the first argument (of type error) “is assignable to the value pointed to by” a second argument of type *error.

Loading

@bcmills
Copy link
Member

@bcmills bcmills commented Aug 4, 2021

Loading

@neild
Copy link
Contributor

@neild neild commented Aug 4, 2021

That does indeed seem to contradict the wording of errors.As.

The relevant wording is: "An error matches target if the error's concrete value is assignable to the value pointed to by target"

In this case, the target has type *error so the concrete value is trivially assignable to *target. A vet check for this case seems like a good idea, since there is never a reason to pass an *error as the second parameter of errors.As.

Loading

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

@neild I'm not really trying to nitpick wording, but I think it is unexpected that an error might have an As method which is not called if the error is assignable. I would imagine it to work like json.Unmarshaler, where the default behavior is simply overwritten by the optional interface.

Loading

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

(If I were to nitpick wording, I'd argue that "X is the case if A or B. In the latter case, the method As is responsible…" seems to imply to me, that As should be called if B is true to determine the outcome of the conversion, regardless of whether or not A is also true)

Loading

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

@bcmills Yes, @andig also suggested that check, though I didn't understand what he was saying at first. I do think it's a good idea. I still find the current behavior of As surprising.

Loading

@bcmills
Copy link
Member

@bcmills bcmills commented Aug 4, 2021

I think the current behavior is appropriate, if for no other reason than that pre-errors users of various APIs would use type-assertions to check whether an error is of a given type. Only falling back to the custom As method if the value is not assignable keeps As more consistent with those users, and makes it more of a drop-in replacement for a type assertion.

Loading

@neild
Copy link
Contributor

@neild neild commented Aug 4, 2021

It was (for better or worse) an intentional choice that Is and As methods can't override the default behavior.

Loading

@Merovius
Copy link

@Merovius Merovius commented Aug 4, 2021

Fair enough. I find it very strange, but I guess I'm too late for that discussion.

Then perhaps the issue should be re-titled again, as the go vet check is apparently the only useful thing we can do here.

Loading

@gopherbot
Copy link

@gopherbot gopherbot commented Aug 4, 2021

Change https://golang.org/cl/339889 mentions this issue: go/analysis/passes/errorsas: warn if errors.As target is *error

Loading

@neild neild changed the title proposal: errors: implement As for errorString proposal: cmd/vet: warn when errors.As target has type *error Aug 4, 2021
@rsc rsc moved this from Incoming to Active in Proposals Aug 4, 2021
@rsc
Copy link
Contributor

@rsc rsc commented Aug 4, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

Loading

@bcmills
Copy link
Member

@bcmills bcmills commented Aug 4, 2021

Would it make sense to apply the same warning to pointers to empty interface types, too?
They are trivial in essentially the same way, in that they cause errors.As to decay to a nil-check.

Loading

@neild
Copy link
Contributor

@neild neild commented Aug 4, 2021

Warning on *interface{} doesn't seem like it would have any false positives, but I suspect it's unlikely to show up in practice.

Loading

@rsc
Copy link
Contributor

@rsc rsc commented Aug 11, 2021

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

Loading

@rsc rsc moved this from Active to Likely Accept in Proposals Aug 11, 2021
@rsc rsc moved this from Likely Accept to Accepted in Proposals Aug 18, 2021
@rsc
Copy link
Contributor

@rsc rsc commented Aug 18, 2021

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

Loading

@rsc rsc changed the title proposal: cmd/vet: warn when errors.As target has type *error cmd/vet: warn when errors.As target has type *error Aug 18, 2021
@rsc rsc removed this from the Proposal milestone Aug 18, 2021
@rsc rsc added this to the Backlog milestone Aug 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Proposals
Accepted
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants