rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.2k stars 12.81k forks source link

Known limitations of branch coverage instrumentation #124118

Open Zalathar opened 7 months ago

Zalathar commented 7 months ago

Branch coverage is known to support these branching language constructs:

Branch coverage is known to not yet support these branching language constructs:


Relevant PR links:

(Rewritten on 2024-07-11; see edit history for the old text.)

Zalathar commented 7 months ago

Based on my investigations so far:

Zalathar commented 7 months ago

There's also some discussion at https://github.com/rust-lang/rust/issues/79649#issuecomment-2061486639 about whether the right-hand-side of a lazy boolean expression (&&/||) should be treated as a “branch” condition, even when it's not part of an if condition.

My view is that “branch coverage” should not be in the business of instrumenting things that clearly aren't branches at the language level, at least not by default. There are valid use-cases for that behaviour, but if it exists then it should require some additional level of opt-in beyond just enabling branch coverage.

(If people want to discuss this point further, I would suggest creating a separate issue for that discussion.)

Lambdaris commented 7 months ago

Is there a draft folk for if-let pattern?

I have tried a bit and found a possibly feasible way to insert block markers, see a naive draft.

Because if let would be taken as match guard this method may solve them together (not sure, I just check it could find the true/false blocks for code like if let (En::A(Some(a)) | En::B(a), En::C(b)) = _.

By this then BranchSpan might need to change its true_marker and false_marker to Vec<BlockMarkerId>.

Zalathar commented 7 months ago

I managed to get my match-arm idea working, so I'll try to tie up the loose ends and post it as a PR.

Zalathar commented 7 months ago

124154 shows my current approach to instrumenting match arms for branch coverage.

Zalathar commented 7 months ago

I now have draft PRs for if-let, let-else, and match arms (without or-patterns).

ZhuUx commented 7 months ago

Noticed the match arms pay attention to occasion like comment at first line of make_expression in counters.rs, I think it may be better put a note here rather reviews of the current two prs.

Many redundant expressions like this may be introduced by match arms. The root cause is rustc may generate several test blocks for certain pattern. For example, code (A | B, C | D) will generate CFG like code

if match A {
    if match C {
        // Assume here is BcbCounter 1
    }  else if match D {
        // ...
    }
} else if match B {
    if match C {
        // Assume here is BcbCounter 2
    }  else if match D {
        // ...
    }
}

The true_term of C apparently should be BcbCounter 1 + BcbCounter 2. Pattern (A | B, C | D, E | F) even make terms of E more sophisticated. Actually they are just composition of several BcbCounter::Counter thus there probably many of intermediate expressions can be optimized out.

However, given that Expressions does not have runtime overhead the main side effect of these redundant expressions might only make people who read mir directly distressed. Further if we wanted to minimize the number of expressions, probably do it in llvm is better.

ZhuUx commented 7 months ago

The true_term of C apparently should be BcbCounter 1 + BcbCounter 2. Pattern (A | B, C | D, E | F) even make terms of E more sophisticated. Actually they are just composition of several BcbCounter::Counter thus there probably many of intermediate expressions can be optimized out.

This might not be "apparent". We also can view C as several branches sharing one span. By this view most work could be simplified but users would be confused with many branches in llvm-cov reports.

Zalathar commented 4 months ago

I've rewritten the issue description to give a more up-to-date summary of the status quo.