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

System.IndexOutOfRangeException #135

Closed
kentahikaru opened this issue Dec 2, 2021 · 48 comments
Closed

System.IndexOutOfRangeException #135

kentahikaru opened this issue Dec 2, 2021 · 48 comments
Labels
bug Third party Problem lies in a consumed library which may or may not have a work-round

Comments

@kentahikaru
Copy link

Hello, i'm trying to do code coverage by our tests. We are using .NET Framework 4.8 and NUnit.
When i run tests it seems to copy assemblies (we have quite a lot of them) but during that process altcover crashes with exception:

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Mono.Cecil.Cil.InstructionCollection.ResolveInstructionOffset(InstructionOffset inputOffset, InstructionOffsetCache& cache)
at Mono.Cecil.Cil.InstructionCollection.UpdateLocalScope(ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, InstructionOffsetCache& cache)
at Mono.Cecil.Cil.InstructionCollection.UpdateLocalScope(ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, InstructionOffsetCache& cache)
at Mono.Cecil.Cil.InstructionCollection.UpdateLocalScopes(Instruction removedInstruction, Instruction existingInstruction)
at Mono.Collections.Generic.Collection1.Insert(Int32 index, T item) at [email protected](Instruction next, Instruction i) in /_//AltCover.Engine/CecilEx.fs:line 105 at Microsoft.FSharp.Collections.SeqModule.Fold[T,TState](FSharpFunc2 folder, TState state, IEnumerable1 source) in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\seq.fs:line 731 at AltCover.CecilExtension.bulkInsertBefore(ILProcessor ilProcessor, Instruction target, IEnumerable1 newInstructions, Boolean updateReferences) in ///AltCover.Engine/CecilEx.fs:line 99
at AltCover.Instrument.I.visitMethodPoint(InstrumentContext state, StatementEntry e) in /
//AltCover.Engine/Instrument.fs:line 827
at AltCover.Instrument.I.instrumentationVisitorWrapper(FSharpFunc2 core, InstrumentContext state, Node node) in /_//AltCover.Engine/Instrument.fs:line 1275 at [email protected](T node) in /_//AltCover.Engine/Visitor.fs:line 1473 at Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList1 cons, FSharpFunc2 f, FSharpList1 x) in D:\workspace_work\1\s\src\fsharp\FSharp.Core\local.fs:line 241
at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc2 mapping, FSharpList1 x) in D:\workspace_work\1\s\src\fsharp\FSharp.Core\local.fs:line 252
at Microsoft.FSharp.Collections.SeqModule.Fold[T,TState](FSharpFunc2 folder, TState state, IEnumerable1 source) in D:\workspace_work\1\s\src\fsharp\FSharp.Core\seq.fs:line 731
at AltCover.Visitor.visit(IEnumerable1 visitors, IEnumerable1 assemblies) in ///AltCover.Engine/Visitor.fs:line 1459
at [email protected](Unit unitVar0) in /
//AltCover.Engine/Main.fs:line 704
at AltCover.CommandLine.I.doPathOperation[a](FSharpFunc`2 f, a defaultValue, Boolean store) in ///AltCover.Engine/CommandLine.fs:line 235
at AltCover.Main.I.doInstrumentation(String[] arguments) in /
//AltCover.Engine/Main.fs:line 678

@SteveGilham SteveGilham added bug Third party Problem lies in a consumed library which may or may not have a work-round labels Dec 2, 2021
@SteveGilham
Copy link
Owner

Interesting. The error is being provoked at the point where AltCover is inserting a call to its visit recording function, and the exception is raised from within the Mono.Cecil library that AltCover uses for such manipulation, at a point where the Cecil library is keeping track of the changes so that values go out of scope at the appropriate point and not before or after.

The stack trace looks quite close to the one in Cecil's issue 697. That one is now closed, but the fact that it was as regression in a recent release would indicate that that particular part of the library is a currently bit fragile (see also related Cecil issue 781, for example). Those issues resulted from a piece of somewhat idiosyncratic IL in the code being instrumented (that one was from an old version of the Mono C# compiler); this suggests that there is something unexpected and unusual in whichever piece of code and version of compiler being used, such that the general purpose Cecil code hasn't been exposed to IL like it before.

This one is going to be interesting to track down; it may involve me supplying experimental builds with more tracing to find out what's happening. It will certainly involve raising an issue against Mono.Cecil, even if I do manage to find a work-round to fix up the rough patches to prevent the exception being provoked.

Just as a matter of interest, which AltCover version are you using (which will then let me determine which Cecil version is involved as well)?

@kentahikaru
Copy link
Author

kentahikaru commented Dec 3, 2021

Hello , my version is:

PS > altcover.exe Version
AltCover version 8.2.831

BTW why is there mono used ?
I'm running .NET Framework 4.8 on windows.

@SteveGilham
Copy link
Owner

Latest AltCover, so that means the latest version of Cecil, v 0.11.4, which fixes one, but not the other of the two issues mentioned above.

Mono.Cecil is a general purpose library for manipulating .net assemblies; while the development of that library was incubated under the Mono Project, it doesn't make use of the old Mono cross-platform runtime in any way; the version in AltCover targets the general netstandard2.0 API.

@kentahikaru
Copy link
Author

Could the problem be that we are NOT using netstandard2.0 ?

@SteveGilham
Copy link
Owner

The netstandard2.0 API is a subset of .net Framework 4.7.2 -- it's a target that is there for library writers to develop code that will work equally well on the old .net Framework, and the new cross-platform .net, so that is as likely to be an issue as which processor the code that's provoking the issue was compiled on (i.e. not at all, in theory, since we're working levels of abstraction above that).

BTW, I'm prototyping a work-round as we speak, so there should be a build to experiment with within the day.

@SteveGilham
Copy link
Owner

Here's a first prototype build which will attempt to fix the problem before it arises, and report (as a warning) any method it needs to fix. The NuGet package is inside a .zip so it can be uploaded here.

altcover.8.2.8007.12409.nupkg.zip

If you could try it and report results, that would be great.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 3, 2021

Hello , i tried to run this new version but it seems to hand somewhere:
it's just eating a lot of CPU and memory usage is rising. The output stopped somewhere around memory = 3600MB

@SteveGilham
Copy link
Owner

SteveGilham commented Dec 3, 2021

Suitably malformed debug symbol information (which is what we are trying to fix) can cause the process to go into a spin making no progress, though I would think that more likely for a deliberately obfuscated assembly than one that is the outcome of a normal build process. If any of the assemblies are obfuscated, it would make sense to exclude those from instrumentation too (or test against an unobfuscated build if those obfuscated assemblies are your own product under test)

When i run tests it seems to copy assemblies (we have quite a lot of them)

Yes, the process works by cloning the indicated directory, rewriting assemblies to inject code to record the visits made in the process of running those assemblies. It makes sense to exclude third-party libraries (like NUnit) from the instrumentation, either by assembly name, like -s "nunit", or, if you're not compiling with deterministic build or similar, by the fact that the source won't be present locally (--localSource), because there's no real point in gathering test information about them.

Here's an updated build with changes to help combat cases that would go into a spin (like mutually nested scopes),

altcover.8.2.8007.22223.nupkg.zip

and which produces a lot more output in order to track progress, including the name of each method being processed and the local scopes within them, like this

Examining method "N.DU/MyUnion N.DU/MyUnion::as_bar()"
Examining scope 0 -1
Examining scope 48 86
Examining scope 101 132
Examining scope 147 178
Pruning scope 0 -1
Pruning scope 48 86
Pruning scope 101 132
Pruning scope 147 178
Pruning scope 12 -1
Pruning scope 72 122
Pruning scope 137 180
Pruning scope 195 250

which should make clear the difference between being in a spin (likely outputting many lines like "Examining scope -1 -1" or "scope already visited") and just having a large number of methods to deal with.

In the example above, we have 4 scopes (indicated by offsets into the method, -1 meaning at the end) checked over, none needing fixing to bypass the bug as reported, then they are checked to prevent the other known Cecil issues, both before and then after the instrumentation pass (the "after" pass showing larger offset numbers where the instrumentation has been injected as blocks of 12 bytes at a time).

Even though this is still at the prototype stage, I've done a little bit to help reduce extra memory use from these countermeasures, knowing how reluctant the .net garbage collector is to run when there's no real memory pressure.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 7, 2021

Hello, sorry for late reply. I broke tests and was fixing it.

I fun your new version with our tests:
How am i running it:
c:\Nugets\altcover.8.2.8007.22223\tools\net472\AltCover.exe -i "...\Projects\AppStudio\CurrentBuild\Bin" -o "...\IntegTestsWorkingDir" -- altcover.exe runner -o "...\IntegTestsTemp\coverage.xml" -- "c:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe" "...\BuildOutput\Bin...IntegrationTests.dll"

The last thing i see in terminal output is:
...
Pruning scope 0 -1
Examining method "System.Void .../d__19::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
Examining method "M/d__20::MoveNext()"
Examining scope 0 -1
Pruning scope 0 -1
Pruning scope 0 -1
Examining method "System.Void M/d__20::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"

@SteveGilham
Copy link
Owner

It looks like we have two problems here. One is the problem with broken scopes, whatever is causing that, and the second is that for some reason the process is getting stuck while work on some of the compiler generated code for an async method.

As the original run that led to the report didn't hang, but just threw, it is quite likely that the code that caused the original problem had already been found and dealt with before reaching this point -- there may be one or more "Resolving problem found in method" messages further up the log. It would be worth having a look for such things to see if I can close that side of things off.

As to the new issue, that d__20::SetStateMachine shows no logging at all just follows the example of d__19::SetStateMachine -- those generated methods don't have debuggable content and are typically trivial, so having no scopes would be expected. That also means that it's likely that the place where the code hangs is something after that method was examined and before the next one is, which may mean in the process of moving to another class or assembly as well.

So, here's another build which adds more logging to the interesting parts between the individual methods

altcover.8.2.8011.25556.nupkg.zip

The new logging for the equivalent part of an async construct, and moving to the next thing to work on (taken from one of the AltCover build tests) looks like this --

...
Considering method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
         within method Some
  System.Threading.Tasks.Task NUnitTestProject1.Tests::AddAsync_Returns_The_Sum_Of_X_And_Y()
         within method None
Selected method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
Visiting method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
Recording method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
Instrumenting method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
Examining method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
Examined method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
Pruning method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
Pruned method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
After Recording method
After Instrumenting method System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
Pruning method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
Pruned method "System.Void NUnitTestProject1.Tests/<AddAsync_Returns_The_Sum_Of_X_And_Y>d__1::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)"
After Recording type
After Instrumenting type
Visiting type NUnitTestProject1.Tests/<Yielder>d__4
Recording type NUnitTestProject1.Tests/<Yielder>d__4
Instrumenting type
Considering method System.Void NUnitTestProject1.Tests/<Yielder>d__4::.ctor(System.Int32)
...

@kentahikaru
Copy link
Author

kentahikaru commented Dec 8, 2021

Hello, i ran the last version you sent me with following results:

There is no "Resolving problem found in method" in the vicinity of "SetStateMachine" method.
In this run,
The line number in log for

Considering method System.Void M/<G>d__20::SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)

is 3597548

The last mention of "Resolving problem found in method" is on line number 2629654 .. so "quite far away"

So i ran the program with following log:
in the end, it just keeps repeating following lines:
System.Threading.Tasks.Task My/<>c__DisplayClass24_0::<G2>g__P|0(I) within method Some System.Void M/<>c__DisplayClass24_0/<<G2c>g__P|0>d::MoveNext() within method Some
The "tail" of the log:
I is referencing variable(s) of the same type. We use it to recursively traverse "file tree" like structure.

System.VoidM/<>c__DisplayClass24_0/<<G2>g__P|0>d::MoveNext() within
System.Threading.Tasks.Task M/<>c__DisplayClass24_0::<G2>g__P|0(I) within
System.VoidM/<>c__DisplayClass24_0/<<G2>g__P|0>d::MoveNext() within
System.Threading.Tasks.Task M/<>c__DisplayClass24_0::<G2>g__P|0(I) within

@SteveGilham
Copy link
Owner

SteveGilham commented Dec 8, 2021

This is the outcome I'd been expecting from this time around.

  • The "Resolving problem found in method" messages earlier in the trace suggests that the original issue has been found and resolved; that part now just needs polishing for release
  • The stop-and-spin problem that the process now hits, having passed the original hurdle, is indeed a separate issue

The new issue is in the process of associating a compiler generated method with the one in the original source code from which it was created. The type M/<>c__DisplayClass24_0 has a method <G2>g__P0, and that is associated with the MoveNext() member of the nested class M/<>c__DisplayClass24_0/<<G2>g__P|0>d which presumably is built from the same line(s) of code, and this things go around in circles.

Looking at the types, the name <>c__DisplayClass24_0 indicates a lambda expression, the <G2> that it is ultimately within method G2 and the fragment g__P|0 indicates there is an inner function called P somewhere in the mix.

Am I correct in thinking that the G2 looks something like this

  <visiblity> async <return type> G2(<arguments>)
  {
    ...
    async <return type> P(<arguments>)
    {
      ...
      // code with lambda expression e.g. await Task.Run(() => Thread.Sleep(2000));
      ...
    }
    ...
  }

which throws more of the individual elements into the mix than the cases I have so far tested with.

I shall experiment with this construction, but would be grateful for a similarly stripped down version of the actual code of that method.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 8, 2021

Hello,
it's kind of correct, it's more like:

  <visiblity> async <return type> G2(<Arguments>)
  {
    ...
    await P(r)
    ...
    async <return type> P(<Arguments2>)
    {
      ...
      Here is some linq with lambda filtering something from Arguments
      // code with lambda expression e.g. await Task.Run(() => Thread.Sleep(2000));
      retun if nothing to do
      
      foreach(var item in <Arguments2>)
     {
         await P(item);
     }
      ...
    }
    ...
  }

@SteveGilham
Copy link
Owner

Alas, there seems to be some subtlety that these simple sketches are missing. Taking as an example

        internal async Task OuterAsync(IEnumerable<int> data)
        {
            await InnerAsync(data);
            async Task InnerAsync(IEnumerable<int> data2)
            {
                var wait = await Task.Run(() => data2.Where(x => x > 0).Max());
                await Task.Run(() => Thread.Sleep(wait));
                foreach (var i in data2)
                {
                    await InnerAsync(data2.Skip(1));
                }
            }
        }

this lacks the nested type hierarchy like MetaDataHierarchy/<>c__DisplayClass24_0/<<GetMemberCaptionsAsync>g__PopulateCaptionListAsync|0>d -- user class containing a lambda containing an async inner class. Similarly named types exist, but so far they are both nested directly inside the outer class, not one inside the other. It's that class-level nesting which is causing the confusion in your case.
Screenshot 2021-12-08 132519

While I think I may now have enough information to hazard a fix, I'll be less happy with it while I don't yet have an example to test against, so I'll continue to tinker, and look forwards to any more information you are able to provide.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 8, 2021

I think i don't understand the IL syntax.
So you're saying i should have something like :

class M
{
    class DisplayClass
    {
        G()
        {
        ...
        ]
    }
}

what does the c__DisplayCLass mean ?

@SteveGilham
Copy link
Owner

SteveGilham commented Dec 8, 2021

All the type names with angle-brackets in like <>c__DisplayClass24_0 and <<G2c>g__P|0>d are compiler generated; so it's a question of trying to work out what structure of the source code prompts the compiler to do such a thing. Clearly there's some subtlety in your actual code compared with the skeleton you provided earlier and I filled out.

I shall continue to play with various combinations of async, lambdas and inner functions a while longer.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 8, 2021

well we have G in M, but i can't find DisplayClass anywhere ...
where can i find generated IL code of M class ? in /obj ?

@SteveGilham
Copy link
Owner

You can find the IL for an assembly most simply by using the ILDasm tool, or with a decompiler like ILSpy

When the compiler generates code, it uses "unutterable" names for things -- names that are not valid in actual code because they contain some of <>@$ and maybe more -- so cannot cause interference with the code you are writing. In particular, names like <>c__DisplayClass0_0 (where the numbers vary) are the names the compiler gives to lambda objects.

When you write code like () => Thread.Sleep(wait) the compiler turns it into a class like

private sealed class <>c__DisplayClass0_0
{
	public int wait;  // closure -- to be set by caller

	internal void <OuterAsync>b__2()
	{
		Thread.Sleep(wait);
	}
}

and code to create an instance of the type and call it where appropriate, like

	private <>c__DisplayClass0_0 <>8__1;

...
				<>8__1 = new <>c__DisplayClass0_0();
				<>8__1.wait = ...
...
				<>8__1.<OuterAsync>b__2();
...

In the same way, async methods are expanded into a whole class, and a set of calls into that class.

SteveGilham added a commit that referenced this issue Dec 8, 2021
SteveGilham added a commit that referenced this issue Dec 8, 2021
@kentahikaru
Copy link
Author

Hello i think i managed to simulate that behavior:

This one DOES NOT generage DisplayClass


namespace ConsoleApp1
{
    class Program
    {
        public static void Main(string[] args)
        {
            Method1();

            return;

            int Method1()
            {
                return 0;
            }
        }

        
    }
}

This one DOES generate DisplayClass

namespace ConsoleApp1
{
    class Program
    {
        public static void Main(string[] args)
        {
            int number = 10;
            Method1();

            return;

            int Method1()
            {
                --number;
                return 0;
            }
        }

        
    }
}

So it's closure, capturing the variable. I'd guess it's doing that so it can "see" the captured variable?!

@kentahikaru
Copy link
Author

BTW would it be possible to delete this issue after we are done ? there are some info about code that corporate management wouldn't like to see in public :(

@SteveGilham
Copy link
Owner

For the moment, I've sanitized names and removed the images. The original report stack trace is entirely in my code, so can remain along with a simple closed/fixed from me. Meanwhile, I've brought the fix for the original issue up to releasable standards and that's "on the train" for the next release.

@SteveGilham
Copy link
Owner

Some progress this afternoon -- I have the simplest possible example that has the pattern of types noted above, like

System.Void ClassLibrary1.Class3/<>c__DisplayClass0_0/<<OuterAsync2>g__InnerAsync3|1>d::MoveNext()
System.Threading.Tasks.Task ClassLibrary1.Class3/<>c__DisplayClass0_0::<OuterAsync2>g__InnerAsync3|1(<Arguments>)

and it looks like

        internal async Task OuterAsync2(IEnumerable<int> data)
        {
            await Task.Run(() =>
            {
                async Task InnerAsync3(IEnumerable<int> data3)
                {
                    foreach (var i in data3)
                    {
                        await OuterAsync2(data3.Skip(3));
                    }
                }

                InnerAsync3(data); // <== *not* `await`ed 
            });
        }

where the MoveNext is represented by the body of the InnerAsync3 method, and it goes to theTask <OuterAsync2>g__InnerAsync3|1(...), which is the body of the enclosing lambda, the latter then goes unambiguously to another generated class that also spans the lambda, and that in turn goes to the OuterAsync2 method.

It was the not awaiting that made the difference.

@SteveGilham
Copy link
Owner

Based on the analysis so far, here is a speculative build that may address this second issue; or, failing that, provide a little more insight about the sticking point in its more targeted logging.

altcover.8.2.8013.22518.nupkg.zip

@SteveGilham
Copy link
Owner

What tool did you use for sanitation ?

Edit comment, copy all text to editor, search replace and trim excess, paste back into comment.

Hello , i ran latest nuget version you provided with following result:

That accords with my example. So now "all" I have to do is fix what it's doing wrong.

@SteveGilham
Copy link
Owner

This build resolves my copy of the issue. How does it work for you?
altcover.8.2.8014.15250.nupkg.zip

This build doesn't have any extraneous log output, unlike the previous ones.

@kentahikaru
Copy link
Author

Hello, unfortunatelly it doesn't fix the issue.
it hangs on the same file as previous version and memory is rising.

@SteveGilham
Copy link
Owner

I had feared that there might be other oddities lurking elsewhere. Let me reinstate logging and give this another go-around, and see if there is an obvious pattern emerging

altcover.8.2.8014.17303.nupkg.zip

SteveGilham added a commit that referenced this issue Dec 10, 2021
 -- mutually referencing inner types confusing the contained-in algorithm
@kentahikaru
Copy link
Author

kentahikaru commented Dec 10, 2021

I think the output looks the same as before ?!

Considering method System.Threading.Tasks.Task M/<>c__DisplayClass24_0::<GAsync>g__PAsync|0(I)
Candidate 0 for "<GAsync>g__PAsync|0" : System.Void M/<>c__DisplayClass24_0/<<GAsync>g__PAsync|0>d::MoveNext()
within Some
  System.Void M/<>c__DisplayClass24_0/<<GAsync>g__PAsync|0>d::MoveNext()
within Some
  System.Threading.Tasks.Task M/<>c__DisplayClass24_0::<GAsync>g__PAsync|0(I)
Candidate 0 for "<GAsync>g__PAsync|0" : System.Void M/<>c__DisplayClass24_0/<<GAsync>g__PAsync|0>d::MoveNext()
within Some
  System.Void M/<>c__DisplayClass24_0/<<GAsync>g__PAsync|0>d::MoveNext()

@SteveGilham
Copy link
Owner

Yes that looks the same -- which is puzzling me just at the moment. I had been expecting a new case to deal with, since the change should be taking

System.Threading.Tasks.Task M/<>c__DisplayClass24_0::<GAsync>g__PAsync|0(I)

up the class hierarchy to method M::GAsync in preference to going back down to the nested type.

@kentahikaru
Copy link
Author

How about closure i mentioned in #135 (comment)
Could this be the issue ?

@SteveGilham
Copy link
Owner

It is -- and it's there in my example with just a difference in the index number <>c__DisplayClass1_0 vs <>c__DisplayClass24_0; the difference in outcomes seems to have been in a simplifying assumption I had made when preparing the earlier build -- it turned out to have been working by coincidence.

I've reworked it now, fixing another long-latent bug in the process --
altcover.8.2.8014.20272.nupkg.zip

This one still has the logging-to-console going on, just in case.

@kentahikaru
Copy link
Author

kentahikaru commented Dec 10, 2021

So i got this .. it seems to be looping in different class than before.
I'll check tomorrow the structure of code. No energy left today.

[Members ECH::AddEx(MBase,List<Member>,int,int)]
Filter predicate <fun:containingMethod@1006-1>
within Some
  Members ECH::AddEx(MBase,List<Member>,int,int)
within None
Contained by Members ECH::AddEx(MBase,List<Member>,int,int)
Considering method IEnumerable<string> ECH/<>c__DisplayClass14_0::<IsChild>g__Ancestors|0(string)
Looking for names like "IsChild" in type ECH/<>c__DisplayClass14_0 or ECH
candidates
	[]
Filter predicate <fun:containingMethod@978>
Looking for names like "<IsChild>" in type ECH/<>c__DisplayClass14_0 or related types
Candidate 0 for "<IsChild>g__Ancestors|0" : System.Boolean ECH/<>c__DisplayClass14_0/<<IsChild>g__Ancestors|0>d::MoveNext()
within Some
  System.Boolean ECH/<>c__DisplayClass14_0/<<IsChild>g__Ancestors|0>d::MoveNext()
Looking for names like "<IsChild>g__Ancestors|0" in type ECH/<>c__DisplayClass14_0 or ECH
candidates
	[IEnumerable<string> ECH/<>c__DisplayClass14_0::<IsChild>g__Ancestors|0(string)]
Filter predicate <fun:containingMethod@1006-1>
within Some
  IEnumerable<string> ECH/<>c__DisplayClass14_0::<IsChild>g__Ancestors|0(string)
Looking for names like "IsChild" in type ECH/<>c__DisplayClass14_0 or ECH
candidates
	[]
Filter predicate <fun:containingMethod@978>
Looking for names like "<IsChild>" in type ECH/<>c__DisplayClass14_0 or related types
Candidate 0 for "<IsChild>g__Ancestors|0" : System.Boolean ECH/<>c__DisplayClass14_0/<<IsChild>g__Ancestors|0>d::MoveNext()
within Some
  System.Boolean ECH/<>c__DisplayClass14_0/<<IsChild>g__Ancestors|0>d::MoveNext()
Looking for names like "<IsChild>g__Ancestors|0" in type ECH/<>c__DisplayClass14_0 or ECH
candidates
	[IEnumerable<string> ECH/<>c__DisplayClass14_0::<IsChild>g__Ancestors|0(string)]
Filter predicate <fun:containingMethod@1006-1>
within Some
  IEnumerable<string> ECH/<>c__DisplayClass14_0::<IsChild>g__Ancestors|0(string)
Looking for names like "IsChild" in type ECH/<>c__DisplayClass14_0 or ECH
candidates
	[]

@SteveGilham
Copy link
Owner

SteveGilham commented Dec 10, 2021

No energy left today.

I don't blame you.

I assume that type ECH does have a IsChild member; but that it isn't async (given the name) but the Ancestors inner function is.

With that as a lead, I've been able to concoct both synchronous and async examples, with the key ingredient being an async inner function which closes over a value in the surrounding function. That generates a closure type <>c__DisplayClass#_# to hold the data closed over, and that type has a nested <<Outer>g__Inner|0>d type which it instantiates to perform the asynchronous operation.

The difference between the two cases is that an async function generates an <Outer>_d named type to manage that asynchronous behaviour. The <Outer>_d and <>c__DisplayClass#_# type both nest immediately within the type that holds all the methods. My original logic made the progression in the logical called-by chain <<Outer>g__Inner|0>d to <>c__DisplayClass#_# to <Outer>_d to top-level method.

In order to handle the synchronous case, the code now looks from the <>c__DisplayClass#_#::<Outer>g__Inner|0 method directly to the top-level Outer; this works for both the examples I have. Let us hope that this doesn't break the case we had working earlier.

Whenever you are ready -- altcover.8.2.8014.24458.nupkg.zip

All being well, the sites of interest should look like

Looking for names like "Outer" in type T/<>c__DisplayClass#_0 or T
candidates including parent type
	<return type> T::Outer(<argument types>)
Filter predicate <fun:containingMethod@984>

@SteveGilham
Copy link
Owner

Just had an idea about breaking what I sent earlier, and have a wicked test case that does break things. I've seasonal things that need doing over the weekend and into next week, so turn-round may be slower than these last few days.

@kentahikaru
Copy link
Author

Good luck with "the things" and big thank you for amazing support so far :)

@SteveGilham
Copy link
Owner

Working through the new test case took less time than I'd expected.
altcover.8.2.8015.11687.nupkg.zip
This build still has the logging in case there's some extra confounding combination of circumstances that I've not been able to throw at it. If there are still problems, then service may be slower than usual.

@kentahikaru
Copy link
Author

Working through the new test case took less time than I'd expected. altcover.8.2.8015.11687.nupkg.zip This build still has the logging in case there's some extra confounding combination of circumstances that I've not been able to throw at it. If there are still problems, then service may be slower than usual.

That seems to be a success. It passed analysis/instrumentation and is running command after "--" , tests

@SteveGilham
Copy link
Owner

That's good to hear. It may be a few days, but I should have a new release out this side of Christmas with this change in.

@kentahikaru
Copy link
Author

awesome. looking forward to it. Thank you for your hard work! :)

SteveGilham added a commit that referenced this issue Dec 11, 2021
 -- containing methods for the case of inner methods that are `async` and also closures
 -- the async and the closure are two sibling types in the main type
SteveGilham added a commit that referenced this issue Dec 12, 2021
 -- containing methods for the case of inner methods that are `async` and also closures
 -- the async and the closure are two sibling types in the main type
@SteveGilham
Copy link
Owner

This fix is now available in release v8.2.833,

@SteveGilham SteveGilham added the ready to close Believed addressed, waiting to hear to the contrary label Dec 12, 2021
@kentahikaru
Copy link
Author

Hello, is it not possible to install altcover as global tool ?

C:> altcover Version
AltCover version 8.2.831
PS C:> dotnet tool install -g altcover --version 8.2.833
error NU1212: Invalid project-package combination for altcover 8.2.833. DotnetToolReference project style can only contain references of the DotnetTool type
The tool package could not be restored.
Tool 'altcover' failed to install. This failure may have been caused by:

  • You are attempting to install a preview release and did not use the --version option to specify the version.
  • A package by this name was found, but it was not a .NET tool.
  • The required NuGet feed cannot be accessed, perhaps because of an Internet connection problem.
  • You mistyped the name of the tool.

For more reasons, including package naming enforcement, visit https://aka.ms/failure-installing-tool

@SteveGilham
Copy link
Owner

For that you want

dotnet tool install --global altcover.global --version 8.2.833

It's a separate package exactly because of the "DotnetToolReference project style can only contain references of the DotnetTool type" limitation.

@kentahikaru
Copy link
Author

aaa thank you

@kentahikaru
Copy link
Author

Hello, i ran version 8.2.833. It works. Didn't crash or hang, ran tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Third party Problem lies in a consumed library which may or may not have a work-round
Development

No branches or pull requests

2 participants