-
-
Notifications
You must be signed in to change notification settings - Fork 477
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
[Pester 4.0.3] Call depth overflow on $xmlObject | Should be $xmlObject #785
Comments
The problem seems to manifest in this recursive call in InModuleScope Pester {
Describe ArraysAreEqual {
It '[xml] object is itself.' {
$doc = [xml]''
ArraysAreEqual -First $doc -Second $doc
}
}
} |
I think the original repro does not fail with Pester 3.4.3 because 3.4.3 predates array comparison. As of 2541ef9 Pester seems to treat most IEnumerables the same as arrays for comparison. I think that might not be a great strategy. It looks to me like |
I think this could be adequately mitigated by implementing a recursion limit that, when reached, throws an understandable error. That would cause a fast failure instead of pinning one CPU until a call depth overflow occurs. The call depth overflow can take minutes to occur, so a fast failure would be a significant improvement. |
@alx9r Been looking at this to see if there is a simple way of seeing what PowerShell wraps in For the Thoughts? |
One thing that I also tried was simply checking the result of |
self-limit recursion depth to cause fast(er) failure in case cyclic object graphs are encountered
@nohwnd I don't think the cause of the infinite recursion involves whether a collection is considered enumerable. Given the current algorithm's dependence on indexing into the collection, I think the most appropriate test would be for the Here's a test of a bunch of collections I've encountered using PowerShell: Describe 'IList' {
$h= @{}
foreach ( $values in @(
@($false,$true,(New-Object string 'TenTwentyThirty')),
@($true,$true,([System.Collections.ArrayList]@(10,20,30))),
@($false,$false,(New-Object System.Collections.BitArray 16)),
@($false,$false,([System.Collections.Hashtable]@{ten=10;twenty=20;thirty=30})),
@($false,$true,([System.Collections.Queue]@(10,20,30))),
@($false,$false,([System.Collections.SortedList]@{ten=10;twenty=20;thirty=30})),
@($false,$true,([System.Collections.Stack]@(10,20,30))),
@($false,$false,(& {
$d = New-Object "System.Collections.Generic.Dictionary``2[System.String,int32]"
('ten',10),('twenty',20),('thirty',30) | % {$d.Add($_[0],$_[1])}
$d
})),
@($true,$true,(& {
$l = New-Object "System.Collections.Generic.Queue``1[int32]"
10,20,30 | % {$l.Enqueue($_)}
$l
})),
@($true,$true,(& {
$l = New-Object "System.Collections.Generic.List``1[int32]"
10,20,30 | % {$l.Add($_)}
$l
})),
@($false,$false,([xml]''))
))
{
$isIList,$subscriptWorks,$object = $values
$type = $object.GetType()
Context "$($type.Name), $($type.BaseType)" {
It "$isIList, is IList" {
[bool]$type.GetInterface('IList') | Should be $isIList
}
It "$subscriptWorks, subscript operator returns something" {
[bool]$object[0] | Should be $subscriptWorks
}
}
}
} I think |
@alx9r Revisited the problem and I think my implementation is correct, pls review :) It does matter, because when there is mismatch between what we consider a collection and what powershell considers a collection, then on the recursive call we wrap the object in an array, then we unwrap it, incorrectly mark it as an array and call This implementation highlights the problem. The function IsCollection ($o) {
$o -isnot [string] -and $o -is [Collections.IEnumerable]
}
function ArraysAreEqual ([object[]] $o, $i=0) {
"Call $i"
"Object is: " + $o.GetType().FullName
$first = $o[0]
"First is: " + $first.GetType().FullName
if ($i -gt 2) { throw "call is recursive" }
if (IsCollection $first)
{
ArraysAreEqual $first (++$i)
}
}
"`nUsing Int:"
ArraysAreEqual 1
"`n`nUsing String:"
# this would have failed but we explicitly exclude string
# in IsCollection function
ArraysAreEqual "abc"
"`n`nUsing Array:"
ArraysAreEqual 1,2,3
"`n`nUsing List:"
ArraysAreEqual ([System.Collections.Generic.List[int]]@(1,2,3))
"`n`nUsing XmlDocument:"
ArraysAreEqual ([xml]'') You can trigger overflow on string as well if you remove it from the Now if you try and take different collections and enumerable types and cast them to So the fixed code should be what is currently in the master: function IsCollection ($o) {
$o -is [array]
}
function ArraysAreEqual ([object[]] $o, $i=0) {
"Call $i"
"Object is: " + $o.GetType().FullName
$first = $o[0]
"First is: " + $first.GetType().FullName
if ($i -gt 2) { throw "call is recursive" }
if (IsCollection $first)
{
ArraysAreEqual $first (++$i)
}
}
$innerList = [System.Collections.Generic.List[string]]("hello", "its", "me")
$list = [System.Collections.Generic.List[System.Collections.Generic.List[string]]]$innerList
ArraysAreEqual $list Unless we want to consider types that Powershell does not consider collections as collections (say Dictionary). |
@nohwnd I think your analysis is correct. I misunderstood how that algorithm was expected to work. This leaves me with two remaining concerns: Naming and cyclic object graphs. Shouldn't The specter of infinite recursion seems to remain for cyclic object graphs. The head of master currently fails by call depth overflow on this:
Because of this I think there ought to be a self-imposed recursion limit. I've updated PR #824 with all of this in mind. Let me know what you think. |
Don't worry, took me quite a few looks before I understood why precisely it breaks :)
Yes, IsArray is a better name, and I think we should put a short explanation and link to this issue there as well. The self imposed recursion limit is a good idea, I did not like it at first, but I didn't realize how simple it is to create cyclic arrays :) ( the recursion limit seems to be over 5000 calls on my computer, so it takes a while till it hangs/throws. )
100 seems to be enough, as we can reasonably expect that people won't be creating 100 layer arrays on purpose.
I would just make the error message more descriptive, but that can be fixed by someone else.
And I would make the recursion depth parameter explicit in the function and make it default to 0, to avoid get-variable.
|
Those all look like good recommendations. Thank you. |
@it-praktyk That suggestion prompted this change, but I'm not sure exactly what @nohwnd had in mind. |
@it-praktyk The error seems fine now, the small suggestion of what probably have happened is what I was looking for 👍 |
@nohwd Oh right. Nice spot. Thanks for the merge. :) |
Repro
Expected Result
I expected this test to pass. It passes using Pester 3.4.3.
Actual Result
Environment
The text was updated successfully, but these errors were encountered: