We allow branches to join where one holds a managed lock but the other
doesn't, but we can't do so for back edges: because there we can't drop
them from the lockset, as we have already analyzed the loop with the
larger lockset. So we can't allow dropping managed locks on back edges.
We move the managed() check from handleRemovalFromIntersection up to
intersectAndWarn, where we additionally check if we're on a back edge if
we're removing from the first lock set (the entry set of the next block)
but not if we're removing from the second lock set (the exit set of the
previous block). Now that the order of arguments matters, I had to swap
them in one invocation, which also causes some minor differences in the
tests.
One might ask: what about asserted capabilities? I plan to introduce a warning when they are released, because that can't be consistent, and then they can't disappear on back edges without warning.
For negative capabilities we'd presumably see a warning for the "positive" capability instead.
Not sure how universal capabilities are typically used. Presumably one could release such a capability in a loop? Then on the other hand, code using such capabilities is probably not very interested in false negatives.