-
Notifications
You must be signed in to change notification settings - Fork 71
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
bug: Prevent nil-pointer when using custom errors from sub function #252
Conversation
We have created an issue in Pivotal Tracker to manage this: https://www.pivotaltracker.com/story/show/185322738 The labels on this github issue will be updated when the story is started. |
This fixes a curious behaviour that I can't remember coming across, although it is mentioned in the Go FAQ. I have a slight hesitation in adding
According to the Go FAQ:
I'm in two minds about this. On one hand, I like the idea of brokerapi being robust and handling situations like this. On the other hand, using These are just my personal thoughts, and I could be persuaded in either direction. I'd be interested to hear what other folks think, and whether there are good examples or precedents to follow. |
I would be more comfortable with something slow (when using custom errors) than crashing. |
Ideally folks should not create a nil apiresponses.FailureResponse, but it is possible, and when that happens, calling methods on the nil apiresponses.FailureResponse should not panic. Relates to #252
I was reflecting on this, and part of the challenge for me is that if we replace: if err != nil { with err != nil && !reflect.ValueOf(err).IsNil() { in one place, then potentially this change should be made in all Go code. And even the basic tutorials should be updated. I see that there's a debate about whether this is a design bug in Go, and I don't want to get into that debate because Go is not about to change. I understand how the change fixes the bug that you observed, but I hope you can understand that I'm hesitant about something like this as it would look unusual to a seasoned Go developer. So I tried to think of alternatives that don't have such broad ecosystem implications. Clearly there was a problem that led to this PR being created, and it would be good to make that problem better. My guess is that this originally came from a function that looked a bit like this: func(_ context.Context, _ string, _ domain.ProvisionDetails, _ bool) (domain.ProvisionedServiceSpec, error) {
var err *apiresponses.FailureResponse
// some stuff that assigns to err if there was an error
return domain.ProvisionedServiceSpec{}, err
} This function always causes a panic (unless I think there are two problems here:
I've put together a draft PR that addresses (1), so that if a What do you think? I realise it's not exactly what you wanted, but hopefully it's a step in the right direction. |
Also I think another bug is this function definition in func NewFailureResponse(err error, statusCode int, loggerAction string) *FailureResponse It should be: func NewFailureResponse(err error, statusCode int, loggerAction string) error And then it would discourage folks from having |
I guess the change in |
Could you expand on that a bit. I want to be sure that I've understood you correctly. |
The function NewFailureResponse() creates an error. The [Go FAQ](https://go.dev/doc/faq#nil_error) says: > It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type *os.PathError. Returning the concrete type can result in subtle failures. For example, consider the code: ```go func something() error { var err *apiresponses.FailureResponse if 1 != 1 { err = apiresponses.NewFailureResponse(errors.New("something bad has happened with the universe"), http.StatusInternalServerError, "log-key") } return err } func main() { if err := something(); err != nil { fmt.Printf("bad thing: %s\n", err.Error()) } } ``` You might expect this to print nothing since 1 does not equal 1. But actually it panics. This is because the nil *apiresponses.FailureResponse is not equal to `nil` because it has a type, but it does have a nil pointer so it panics. If we replace `var err *apiresponses.FailureResponse` with `var err error` then everything works as expected. By returning an `error` type from NewFailureResponse(), the failing scenario doesn't compile. As with many fixes, this could be considered to be a breaking change. But it didn't require code changes on the brokers that I tested with, and if it does break anyone, then it's likely because they have risky code that has the potential to panic in a counterintuitive way. So overall I think the benefit of making this change it worth the risk. Resolves #252 Resolves #256
My current plan is to resolve this with PR #257 I speculated that maybe this problem first occurred for you when writing some code like: func(_ context.Context, _ string, _ domain.ProvisionDetails, _ bool) (domain.ProvisionedServiceSpec, error) {
var err *apiresponses.FailureResponse
if someOperationFailed() {
err = apiresponses.NewFailureResponse(errors.New("something bad has happened with the universe"), http.StatusInternalServerError, "log-key")
}
return domain.ProvisionedServiceSpec{}, err
} The PR changes the return type of NewFailureResponse() to
Because it can break compilation in some circumstances, I'm going to get some second opinions on this as the aim is to fix problems without creating new problems. |
The function NewFailureResponse() creates an error. The [Go FAQ](https://go.dev/doc/faq#nil_error) says: > It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type *os.PathError. Returning the concrete type can result in subtle failures. For example, consider the code: ```go func something() error { var err *apiresponses.FailureResponse if 1 != 1 { err = apiresponses.NewFailureResponse(errors.New("something bad has happened with the universe"), http.StatusInternalServerError, "log-key") } return err } func main() { if err := something(); err != nil { fmt.Printf("bad thing: %s\n", err.Error()) } } ``` You might expect this to print nothing since 1 does not equal 1. But actually it panics. This is because the nil *apiresponses.FailureResponse is not equal to `nil` because it has a type, but it does have a nil pointer so it panics. If we replace `var err *apiresponses.FailureResponse` with `var err error` then everything works as expected. By returning an `error` type from NewFailureResponse(), the failing scenario doesn't compile. As with many fixes, this could be considered to be a breaking change. But it didn't require code changes on the brokers that I tested with, and if it does break anyone, then it's likely because they have risky code that has the potential to panic in a counterintuitive way. So overall I think the benefit of making this change it worth the risk. Resolves #252 Resolves #256
Fixing the following scenario: When returning
nil
as*apiresponses.FailureResponse
from a sub function, the broker can't handle it and produces a nil pointer error.