OpenCover / opencover

A code coverage tool for .NET 2 and above (WINDOWS OS only), support for 32 and 64 processes with both branch and sequence points
https://blog.many-monkeys.com
Other
1.31k stars 247 forks source link

Missing exclusion for delegate cache branch in release builds #586

Open ulrichb opened 8 years ago

ulrichb commented 8 years ago

In 98bd0f01e2c0d236092e1fb932d50f7121cdab55 (feature request #302) support for ignoring delegate cache branches has been added.

The detection seems to miss the situation, when using a static call in a lambda in release (optimized) builds. Tested in VS 2015/Roslyn 1.2.

See the following example.

[TestFixture]
public class ActionLambda
{
    private void M(string p) { }

    [Test]
    public void InstanceCallInsideAction()
    {
        Action a = () => M("");

        a();
    }

    [Test]
    public void StaticCallInsideAction()
    {
        // Here the detection misses the delegate cache branch in an optimized build:
        Action a = () => StaticClass.M("");

        a();
    }
}

public static class StaticClass
{
    public static void M(string p) { }
}

Debug compilation: 2016-05-06 14_58_35-actionlambda - coverage report

Release compilation: 2016-05-06 14_58_51-actionlambda - coverage report

sawilde commented 8 years ago

What does the IL look like for release/debug builds?

ulrichb commented 8 years ago

A side-by-side diff of the StaticCallInsideAction() method (left=Debug, right=Release): 2016-05-06 15_08_06-opencoversample dll - text compare - beyond compare

sawilde commented 8 years ago

Interesting - we can investigate if we can exclude without excluding legitimate cases probably through some code analysis similar to what we have done before

@ddur - what do you think?

ulrichb commented 8 years ago

At the moment, delegate cache branches are ignored by the pattern of the generated IL (see CecilSymbolManager.

Delegate cache fields always start with <>9 (see MakeLambdaCacheFieldName(), LambdaCacheField = '9', and MakeMethodScopedSynthesizedName() in the Roslyn code).

So maybe an alternative to detect the to be ignored brtrue.s branch instruction would be to search for the pattern ldsfld F; dup; brtrue.s LABEL where F is a field reference to a field starting with <>9 (which would never be user code).

ddur commented 8 years ago

Difference is because dll in release build has no sequence-points, therefore source code is not available. Without access to source code, removal of excess branches has limited capabilities.

ulrichb commented 8 years ago

@ddur Isn't the detection of the delegate cache braches based on the IL-instruction pattern (and not on symbols)? Without symbols (removed .pdb), I get no coverage result at all.

ddur commented 8 years ago

@ulrichb Then I do not know. You can ignore my previous post.

sawilde commented 8 years ago

@ulrichb - opencover uses the PDB files to determine the sequence points and it uses the IL to determine the branch points. On some occasions to determine whether to cover or not to cover certain branches, we look at files and we may also look at metadata but the more analysis we do at runtime this will slow the process down so we have to make trade-offs between speed and usefulness.

ALso the PDB also allows us to identify which sequence point relates to which file and line - we (well I) decided no PDB, no coverage required.