Skip to content

Fix: check if target is valid#160

Merged
mtojek merged 25 commits intoelastic:masterfrom
mtojek:fix-159
Apr 15, 2020
Merged

Fix: check if target is valid#160
mtojek merged 25 commits intoelastic:masterfrom
mtojek:fix-159

Conversation

@mtojek
Copy link
Copy Markdown
Contributor

@mtojek mtojek commented Apr 9, 2020

This PR fixes the issue reported in #159 .

Check if the target is valid, before setting the value. The target field will stay nil.

/cc @ruflin

@mtojek mtojek requested review from a team and urso April 9, 2020 16:18
@mtojek mtojek self-assigned this Apr 9, 2020
to.Set(pointerize(to.Type(), v.Type(), v))
if v.IsValid() {
to.Set(pointerize(to.Type(), v.Type(), v))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Given the test case I don't see why v can be invalid. Do we have an original issue with sample code? v should always be assignable to to, and return a zero value at least.

As reifyGetField is an internal function processing a sub-tree only, I wonder if the issue can occur in other edge cases as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do we have an original issue with sample code?

This is the exact extracted part from the linked PR in the issue. Just unpack the given YAML file into a structure with interface{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What I'm wondering about is, if this or a similar error can occur when we unpack into some alternative type. For example if one unpacks into a map, then the call to.SetMapIndex(key, v) at line 224 would be the equivalent for maps. For arrays/slices the equivalent call is to.Index(idx).Set(v) in reifyDoArray. Both calls do not check for an invalid reflect.Value yet. Given the bug at hand, I doubt the other 2 cases have tests that might show an error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've added more test cases, but couldn't reproduce. If you see any potential weird structure that can cause an error, I can add a test case.

Other way round is to wrap all to.Set() calls with a condition checking if the target is valid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If the tests show that we are fine, I'm ok with that.

maybe we can add these combinations for maps as well:

map[string]interface{}{}  , `{"a": []}`
map[string]interface{}{"a": nil}  , `{"a": []}`
map[string][]interface{}{}, `{"a": []}`
map[string][]interface{}{"a": nil}, `{"a": []}`

and for arrays:

[]interface{} , `[[]]`
[][]interface{}, `[[]]`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've updated test cases, but I think we need to review/accept the final unmarshaled forms (I'm referring specifically to this case: map[string]interface{}{"a": nil} where the field disappears).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hm, the field a should not just disappear. If the input configuration does not contain the field, or if it is an empty array, then it should not be overwritten. We want to merge the configuration into the object given. Yet one can discuss what it means to merge an empty array... But it kinda looks like we just found another bug.
I don't want increase the scope for this PR any further. How about skipping this tests that do remove a and create a follow up issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I've just fixed this case by checking if the target is valid:

if v.IsValid() {
    to.SetMapIndex(key, v)
}

assert.Nil(t, verify.Vars[0].Default)
assert.Nil(t, verify.Vars[1].Default)
assert.Equal(t, uint64(3), verify.Vars[2].Default)
assert.Len(t, verify.Vars[3].Default.([]interface{}), 3)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we split this up into 4 test cases (e.g. table driven test), or is the error only triggered by this setup?

Was the error triggered for each of these cases?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Let's add a sample test with an empty array as has been shown in the issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. It seems that an empty array is also an issue. I simplified the test case.


type variable struct {
Default interface{}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

these types are unique to the test case only. Consider moving the type declaration into TestStructEmptyArray

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

@urso
Copy link
Copy Markdown

urso commented Apr 10, 2020

Is this error reproducible if we just unpack into an array, instead of an array within a struct?

@mtojek
Copy link
Copy Markdown
Contributor Author

mtojek commented Apr 10, 2020

Is this error reproducible if we just unpack into an array, instead of an array within a struct?

I replied above. It's reproducible - I simplified the test case, a single line seems to be enough.

@ruflin
Copy link
Copy Markdown
Contributor

ruflin commented Apr 14, 2020

Should we add a chanelog entry?

@urso
Copy link
Copy Markdown

urso commented Apr 14, 2020

Should we add a chanelog entry?

Yes, please.

@mtojek
Copy link
Copy Markdown
Contributor Author

mtojek commented Apr 14, 2020

Should we add a chanelog entry?

Yes, please.

Fixed.

@mtojek
Copy link
Copy Markdown
Contributor Author

mtojek commented Apr 14, 2020

I think we might have hit also this: golang/go@201cb04

@codecov-io
Copy link
Copy Markdown

codecov-io commented Apr 14, 2020

Codecov Report

Merging #160 into master will decrease coverage by 0.10%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #160      +/-   ##
==========================================
- Coverage   78.63%   78.52%   -0.11%     
==========================================
  Files          24       24              
  Lines        2822     2827       +5     
==========================================
+ Hits         2219     2220       +1     
- Misses        440      445       +5     
+ Partials      163      162       -1     
Impacted Files Coverage Δ
reify.go 69.69% <100.00%> (+0.35%) ⬆️
types.go 73.92% <0.00%> (-0.93%) ⬇️
merge.go 79.01% <0.00%> (-0.62%) ⬇️
variables.go 80.28% <0.00%> (+0.35%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a138d3e...9ca7066. Read the comment docs.

This removes testify/assert from only one test file in the ucfg package, and it brings in go-spew to improve the readability of test failures.
error_test.go Outdated
adjusted = strings.Replace(adjusted, "\" in duration \"", " in duration ", 1)
adjusted = strings.Replace(adjusted, "\" accessing", " accessing", 1)
return adjusted
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Which go version failed here? For go tip only? If we need it for tip and it is a known error in tip to be fixed soon I would ignore it for now. We check with tip to see if we might be hit by a change in reflection in the stdlib early in time.

If it fails for a released go version let's add a conditional.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Oh, it's a bug in go1.14 :(

Checking the travis file, I noticed 1.14 is still missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In this case it was "devel". I can adjust the Travis file too, unless you think this should go in a separate PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hm, if it's just tip I'd remove the function altogether. Although I'd prefer to add go 1.14 in another PR, I'd like to have some assurance that 1.14 does not contain the bug :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let me prepare a PR for this.

err = c.Unpack(&verify)
assert.Nil(t, err)
assert.Equal(t, map[string]interface{}{}, verify)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

there is quite some repetition here. How about moving the test cases into a table like so?

cases := map[string]struct{
  input string
  to interface{}
  want interface{}
}{
  "empty map into map of interfaces": {
    input: "{}",
    to: &map[string]interface{}{},
    want: &map[string]interface{}{},
  },
  ...
}

for name, test := range cases {
  t.Run(name, func(t *testing.T) {
    c, err := MustNewConfig(byte(test.input))
    mustUnpack(t, c, test.to)
    assert.Equal(t, test.want, test.to)
  })
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mustUnpack should be available already if you rebase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rebased and refactored as it wasn't exposed.

golden := string(tmp)
assert.Equal(t, golden, message)
message = adjustMessageFormat(message)
assert.Equal(t, golden, message, "Go runtime version: %s", runtime.Version())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💯

@mtojek mtojek requested a review from urso April 15, 2020 12:27
@mtojek mtojek merged commit 1232bd4 into elastic:master Apr 15, 2020
kvch pushed a commit that referenced this pull request Nov 10, 2021
This adds a special case for empty arrays when reifying configuration objects during Unpack. This is a breaking change compared to existing behaviour as visible in the test cases I adjusted. The original issue #160 that introduced said tests is however still addressed.

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

Labels

Team:Elastic-Agent Label for the Agent team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants