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

fix(core): Fix some NPEs in validation and config generation #31

Merged
merged 2 commits into from
Apr 24, 2020
Merged

fix(core): Fix some NPEs in validation and config generation #31

merged 2 commits into from
Apr 24, 2020

Conversation

ezimanyi
Copy link
Contributor

@ezimanyi ezimanyi commented Apr 24, 2020

  • fix(core): Fix some NPEs in validation and config generation

    In a few places in the config validation and translation we were dereferencing a potentially nil pointer. This happens when we we need to reach more than a level deep in the HalConfig and that top-level field is nil.

    A while back we discussed how to handle this, and considered seeing if we could deserialize as values instead of pointers so we could always rely on the zero value. But if I recall we decided against that because:
    (1) It is actually useful to have the nil value because it indicates something was not present in the config and should not be serialized to the service config
    (2) This problem will only happen in cases where we're reaching into the hal config, so the amount of places we need to do this nil check should be reasonable.

    Looking at the generated protos, it looks like there are actually nil-safe accessor generated. If you call x.GetY() it will first check if x is nil, and if so return nil; if not it will return x.y. This took a minute for me to wrap my Java brain around, because the (bad) analogy is basically an instance method on an object checking whether the object is nil. But somehow this works for receivers (I guess because type inference means we know the type of the receiver so which function to call, and we just get the receiver as a possibly-nil object). But this is really nice because it lets you write nil-safe chains as x.GetY().GetZ() that return nil at the end if anything was nil but never throw a nil-pointer panic.

    Also add some test to the validation and parsing modules to validate correct behavior in various edge cases (empty hal config, hal config with providers field but no providers, etc.)

  • refactor(core): Standardize on using pointers

    For a while we weren't sure whether to pass by value or by reference. Given that we're passing around structs whose nested fields are all themselves passed around by reference, it seems logical to do the same at the top level (as it's confusing that the top-level thing is sometimes passed around by value).

    This also resolves a bunch of 'go vet' warnings because the generated structs contain a mutex somewhere, which really should not be copied, and generates a warning when we pass these around by value. If I didn't think that it probably makes sense to pass by value anyway I'd probably spend more time seeing if that warning should be suppressed, but given that I think it makes sense anyway to pass by reference, let's do that.

In a few places in the config validation and translation we were
dereferencing a potentially nil pointer. This happens when we we
need to reach more than a level deep in the HalConfig and that
top-level field is nil.

A while back we discussed how to handle this, and considered
seeing if we could deserialize as values instead of pointers
so we could always rely on the zero value. But if I recall we
decided against that because:
(1) It is actually useful to have the nil value because it
indicates something was not present in the config and should
not be serialized to the service config
(2) This problem will only happen in cases where we're reaching
into the hal config, so the amount of places we need to do this
nil check should be reasonable.

Looking at the generated protos, it looks like there are actually
nil-safe accessor generated. If you call x.GetY() it will first
check if x is nil, and if so return nil; if not it will return
x.y. This took a minute for me to wrap my Java brain around,
because the (bad) analogy is basically an instance method on
an object checking whether the object is nil. But somehow this
works for receivers (I guess because type inference means we
know the type of the receiver so which function to call, and
we just get the receiver as a possibly-nil object). But this
is really nice because it lets you write nil-safe chains as
x.GetY().GetZ() that return nil at the end if anything was nil
but never throw a nil-pointer panic.

Also add some test to the validation and parsing modules to
validate correct behavior in various edge cases (empty hal
config, hal config with providers field but no providers, etc.)
For a while we weren't sure whether to pass by value or by
reference. Given that we're passing around structs whose nested
fields are all themselves passed around by reference, it seems
logical to do the same at the top level (as it's confusing that the
top-level thing is sometimes passed around by value).

This also resolves a bunch of 'go vet' warnings because the generated
structs contain a mutex somewhere, which really should not be copied,
and generates a warning when we pass these around by value. If I didn't
think that it probably makes sense to pass by value anyway I'd probably
spend more time seeing if that warning shoudl be suppressed, but given
that I think it makes sense anyway to pass by value, let's do that.
@maggieneterval
Copy link
Contributor

This took a minute for me to wrap my Java brain around, because the (bad) analogy is basically an instance method on an object checking whether the object is nil. But somehow this works for receivers (I guess because type inference means we know the type of the receiver so which function to call, and we just get the receiver as a possibly-nil object). But this is really nice because it lets you write nil-safe chains as x.GetY().GetZ() that return nil at the end if anything was nil but never throw a nil-pointer panic

Wow, amazing discovery!

Maybe Typescript optional chaining is a more useful analogy?

If I didn't think that it probably makes sense to pass by value anyway I'd probably spend more time seeing if that warning shoudl be suppressed, but given that I think it makes sense anyway to pass by value, let's do that.

Just want to make sure you meant "reference" instead of value in the last sentence of the second commit; otherwise I understand nothing 🤡

Copy link
Contributor

@maggieneterval maggieneterval left a comment

Choose a reason for hiding this comment

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

👨 🐻 📈 💯 💯 💯 🤗
This is fantastic!

@ezimanyi
Copy link
Contributor Author

Ha, good catch on my typo---yes I did mean that we should pass by "reference" so you are understanding correctly!

@ezimanyi ezimanyi added the ready to merge Reviewed and ready for merge label Apr 24, 2020
@mergify mergify bot merged commit 897832a into spinnaker:master Apr 24, 2020
@mergify mergify bot added the auto merged label Apr 24, 2020
@ezimanyi ezimanyi deleted the add-tests branch April 24, 2020 14:55
"github.com/spinnaker/kleat/api/client"
)

func TestEmptyHalConfigToClouddriver(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we'd ever want to refactor these as table driven tests just to emphasize what's actually different about each one and make it clear which cases we'd expect to be added for each new service. On the other hand we'd lose your very nice description of each case. 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good thought! Let me read about table driven tests! These tests were mostly written by seeing what you did for the end-to-end tests in this repo then working from there, so I have a lot to learn about testing in go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto merged ready to merge Reviewed and ready for merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants