Skip to content

Exclude lines & branches that come after calls to methods with [DoesNotReturn] #898

@kevin-montrose

Description

@kevin-montrose

It'd be nice to be able to exclude lines, like return;, that follow methods that contractually never return from coverage. The BCL already ships with the DoesNotReturnAttribute that could be used to indicate this.

I stumble across this a lot when using the "throw helper"-pattern. An example from one my GitHub repos.

PoisonableBase<T> has a method like so:

internal void AssertNotPoisoned<T>(IBoundConfiguration<T> self)
{
    if (Poison != null)
    {
        switch (Poison.Value)
        {
            case PoisonType.Cancelled: 
                Throw.InvalidOperationException<object>("Object is in an invalid state, a previous operation was canceled"); 
                return;
            case PoisonType.Exception: 
                Throw.InvalidOperationException<object>("Object is in an invalid state, a previous operation raised an exception"); 
                return;
            default:
                Throw.ImpossibleException<object, T>($"Unexpected {nameof(PoisonType)}: {Poison}", self);
                return;
        }
    }
}

Each method on Throw never returns.

Even though I do have tests for each PoisonType, my reports will always include each return statement as uncovered:

  • image

For non-void methods I can hack around this restriction by pretending these methods return an object, but for void returning methods you're out of luck.


I suspect this could be hacked into Instrumentor.InstrumentIL, but maybe there's a pass elsewhere that would be a better place.

I think the logic to support this is something like:

  1. For every method body...
    a. Break the IL into runs based on branch targets
    b. In each run, any instructions following a call or callvirt to a method with [DoesNotReturn] is unreachable
    c. Record unreachable instructions somewhere
  2. When instrumenting instructions and branches, lookup reach-ability and skip unreachable instructions

I considered just looking for ret and throw and so on, but I've convinced myself fall-through will break that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementGeneral enhancement requesttenet-coverageIssue related to possible incorrect coverage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions