-
Notifications
You must be signed in to change notification settings - Fork 392
Description
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:
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:
- For every method body...
a. Break the IL into runs based on branch targets
b. In each run, any instructions following acall
orcallvirt
to a method with[DoesNotReturn]
is unreachable
c. Record unreachable instructions somewhere - 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.