Team System Developer Center > Visual Studio Team System Forums > Visual Studio Performance Tools (Profiler) > 100% Code Coverage of switch statement not possible. Unreachable code block
Ask a questionAsk a question
 

Answer100% Code Coverage of switch statement not possible. Unreachable code block

  • Tuesday, June 24, 2008 9:26 AMSonicDeathMonkey Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I have a c# method, as shown below. Code coverage on the method states that I have one missing block.

     

            public void RetrieveAllRegRelationshipInfo(OwnerKeeperSteve registerKeeper)

            {

                switch (registerKeeper)

                {

                    case OwnerKeeperSteve.NA:

                        break;

                }

            }

     

    Looking at the IL, and I am no expert, we get the following:

     

    .method public hidebysig instance void  RetrieveAllRegRelationshipInfo(valuetype [Confused.MotorInsurance.DataContracts]Confused.MotorInsurance.DataContracts.Enumerations.OwnerKeeperSteve registerKeeper) cil managed

    {

      // Code size       12 (0xc)

      .maxstack  2

      .locals init ([0] valuetype [Confused.MotorInsurance.DataContracts]Confused.MotorInsurance.DataContracts.Enumerations.OwnerKeeperSteve CS$4$0000)

      IL_0000:  nop

      IL_0001:  ldarg.1

      IL_0002:  stloc.0

      IL_0003:  ldloc.0

      IL_0004:  ldc.i4.0

      IL_0005:  beq.s      IL_0009

      IL_0007:  br.s       IL_000b

      IL_0009:  br.s       IL_000b

      IL_000b:  ret

    } // end of method RetrieveRegRelationshipInfo::RetrieveAllRegRelationshipInfo

     

    As I said, I am no expert, but doesnt the first br.s statement unconditionally jump to IL_000b, meaning that the highlighted block can not be reached?

Answers

  • Wednesday, July 02, 2008 1:58 AMChris SchmichMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    I recreated your scenario with the following:

     

    Code Snippet

     

    public class CoverageTest

    {

      public enum Bar

      {

        X,

        Y,

        Z

      }

     

      public static void Foo(Bar b)

      {

        switch (b)

        {

          case Bar.X:

            break;

        }

      }

    }

     

     

    With the following test case I received partial coverage (1 block not covered) as you saw:

     

    Code Snippet

     

    [TestMethod()]

    public void FooTest()

    {

      CoverageTest.Foo(CoverageTest.Bar.X);

    }

     

     

    With this test case, I received full coverage:

     

    Code Snippet

     

    [TestMethod()]

    public void FooTest()

    {

      CoverageTest.Foo(CoverageTest.Bar.X);

      CoverageTest.Foo(CoverageTest.Bar.Y);

    }

     

    Under debug builds, the coverage "issue" with the first test case is that it does not hit the following highlighted IL instruction:

     

    Code Snippet

     

    .method public hidebysig static void Foo(valuetype ConsoleApplication33.CoverageTest/Bar b) cil managed

    {

      // Code size 12 (0xc)

      .maxstack 2

      .locals init ([0] valuetype ConsoleApplication33.CoverageTest/Bar CS$4$0000)

      IL_0000: nop

      IL_0001: ldarg.0

      IL_0002: stloc.0

      IL_0003: ldloc.0

      IL_0004: ldc.i4.0

      IL_0005: beq.s IL_0009

      IL_0007: br.s IL_000b

      IL_0009: br.s IL_000b

      IL_000b: ret

    }

     

     

    Switching to a release build (optimizations on), the IL generated is much more efficient and we can get full coverage (as expected) with the first test case alone.

     

    I would recommend collecting coverage numbers against release builds.

     

    Regards,

    Chris

All Replies

  • Friday, June 27, 2008 9:12 AMBill.Wang Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Moved the thread here.

  • Friday, June 27, 2008 9:31 AMBill.Wang Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

     

    I'm not an MSIL expert too. But so far as I know, this is a known issue. Sometimes, not all the generated MSIL code can be covered. You can see it here. Your opinion is on the right track.

  • Friday, June 27, 2008 9:31 AMBill.Wang Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I'm not an MSIL expert too. But so far as I know, this is a known issue. Sometimes, not all the generated MSIL code can be covered. You can see it here. Your opinion is on the right track.

     

    Please ignore this, it's a duplicated reply due to network delay. Sorry.

  • Friday, June 27, 2008 9:38 AMSonicDeathMonkey Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Thanks. I guess that the example I provided is a lot less code to highlight the issue, including much less MSIL too!

     

    So we are saying this is a compiler issue? Does anyone know when we can expect a solution? I would imaging our Java friends would have a field day when they find out the compiler is spewing out unreachable code!

     

    While I understand that 100% coverage is not a statement on code quality in itself, if we drop below 100%, I feel that will be the start of a trend and it will be difficult to measure code quality going forward.

     

  • Monday, June 30, 2008 8:48 PMChris SchmichMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hi SonicDeathMonkey,

     

    Yes, this class of issues boils down to the code being emitted by the compiler.  We've met with the compiler team and are actively working towards a solution.

     

    Do you get different coverage results depending on your solution configuration (Debug vs. Release)?  Are you testing both the positive and negative cases here (i.e. hitting the case statement and not hitting it)?  According to your IL listing, there are no unreachable blocks:

     

    {

      // Code size       12 (0xc)

      .maxstack  2

      .locals init ([0] valuetype [Confused.MotorInsurance.DataContracts]Confused.MotorInsurance.DataContracts.Enumerations.OwnerKeeperSteve CS$4$0000)

      IL_0000:  nop

      IL_0001:  ldarg.1

      IL_0002:  stloc.0

      IL_0003:  ldloc.0

      IL_0004:  ldc.i4.0

      IL_0005:  beq.s      IL_0009

      IL_0007:  br.s       IL_000b

      IL_0009:  br.s       IL_000b

      IL_000b:  ret

    }

     

    Instruction IL_0005 here jumps to instruction IL_0009 if the two values on top of the stack are equal (when the first case statement is hit), so it is reachable.

     

    In your code coverage, what are the statistics for this method?  Do any source lines show up as Not Covered in the source view?

     

    Regards,

    Chris

  • Tuesday, July 01, 2008 11:02 AMChris SchmichMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Moved this thread to the profiler forums.
  • Wednesday, July 02, 2008 1:58 AMChris SchmichMSFT, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    I recreated your scenario with the following:

     

    Code Snippet

     

    public class CoverageTest

    {

      public enum Bar

      {

        X,

        Y,

        Z

      }

     

      public static void Foo(Bar b)

      {

        switch (b)

        {

          case Bar.X:

            break;

        }

      }

    }

     

     

    With the following test case I received partial coverage (1 block not covered) as you saw:

     

    Code Snippet

     

    [TestMethod()]

    public void FooTest()

    {

      CoverageTest.Foo(CoverageTest.Bar.X);

    }

     

     

    With this test case, I received full coverage:

     

    Code Snippet

     

    [TestMethod()]

    public void FooTest()

    {

      CoverageTest.Foo(CoverageTest.Bar.X);

      CoverageTest.Foo(CoverageTest.Bar.Y);

    }

     

    Under debug builds, the coverage "issue" with the first test case is that it does not hit the following highlighted IL instruction:

     

    Code Snippet

     

    .method public hidebysig static void Foo(valuetype ConsoleApplication33.CoverageTest/Bar b) cil managed

    {

      // Code size 12 (0xc)

      .maxstack 2

      .locals init ([0] valuetype ConsoleApplication33.CoverageTest/Bar CS$4$0000)

      IL_0000: nop

      IL_0001: ldarg.0

      IL_0002: stloc.0

      IL_0003: ldloc.0

      IL_0004: ldc.i4.0

      IL_0005: beq.s IL_0009

      IL_0007: br.s IL_000b

      IL_0009: br.s IL_000b

      IL_000b: ret

    }

     

     

    Switching to a release build (optimizations on), the IL generated is much more efficient and we can get full coverage (as expected) with the first test case alone.

     

    I would recommend collecting coverage numbers against release builds.

     

    Regards,

    Chris

  • Wednesday, July 02, 2008 8:20 AMSonicDeathMonkey Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Excellent, thanks very much. I will only go by code coverage results from our continuous integration server from now on, or ensure I am using a release build locally.