opencover
opencover copied to clipboard
There are hidden branches in `Nullable<T>`s
This is more for adding to the collection of subtle things done by the compiler that include extra branches than any serious deficiency in OpenCover, and there is a ready workaround that is arguably better coding practise anyway, but here goes:
My Framework
- [X] .NET 4 Client
- [X] .NET 4.5
My Environment
- [X] Windows 7 or below (not truly supported due to EOL)
- [X] Windows 10
I have already...
- [X] repeated the problem using the latest stable release of OpenCover.
- [X] reviewed the usage guide and usage document.
- [X] have looked at the opencover output xml file in an attempt to resolve the issue.
- [X] reviewed the current issues to check that the issue isn't already known.
My issue is related to (check only those which apply):
- [X] feature request (cf #310 or #352)
Expected Behavior
Minimal C# code fragment (cut down from the real-world example I found it in)
private static bool Match(int item, int? target)
{
if (target.HasValue)
{
return item == target;
}
return false;
}
There is one branch, so "obviously" saturation branch coverage involves two tests, one with, and one without target
having a value.
Actual Behavior
There are two branches, and one path of the compiler generated one doesn't get taken, whatever the input, because it's another HasValue test inside the explicit one (shades of the "is it null?" test lurking in using
statements).
The IL looks like
IL_0000: nop
IL_0001: ldarga.s target
IL_0003: call instance bool valuetype [mscorlib]System.Nullable\u00601<int32>::get_HasValue()
IL_0008: ldc.i4.0
IL_0009: ceq
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: brtrue.s IL_002b
IL_000f: nop
IL_0010: ldarg.0
IL_0011: stloc.2
IL_0012: ldarg.1
IL_0013: stloc.3
IL_0014: ldloc.2
IL_0015: ldloca.s CS$0$0003
IL_0017: call instance !0 valuetype [mscorlib]System.Nullable\u00601<int32>::GetValueOrDefault()
IL_001c: bne.un.s IL_0027
IL_001e: ldloca.s CS$0$0003
IL_0020: call instance bool valuetype [mscorlib]System.Nullable\u00601<int32>::get_HasValue()
IL_0025: br.s IL_0028
IL_0027: ldc.i4.0
IL_0028: stloc.0
IL_0029: br.s IL_002f
IL_002b: ldc.i4.0
IL_002c: stloc.0
IL_002d: br.s IL_002f
IL_002f: ldloc.0
IL_0030: ret
Writing the test as
return item == target.Value;
works and does not generate the hidden branch, and is arguably slightly better coding practice.
Steps to reproduce the problem:
See above.