https://github.com/NagyDonat commented:

I'm really happy that you managed to track down and fix these slow corner 
cases, and overall I like the code changes, but I there were a few places where 
the code was difficult to understand for me (although this may be also related 
to the fact that I didn't sleep enough last night... :wink:).

I added a few inline comments to mark some confusing parts. Moreover, I also 
felt that it would be possible to implement the exhaustion "event" more 
elegantly with a different approach -- and I'll discuss in the rest of this 
"main" review comment.

If I understand correctly, your approach does the following steps:
1. The constructor of `RegionManagerStore` (usually) initializes 
`RegionStoreMaxBindingFanOut`  to `getOptions().RegionStoreMaxBindingFanOut + 
1` -- I think this off-by-one difference between identically named variables is 
a serious drawback of your approach.
2. `RegionManagerStore::getRegionBindings()` uses this adjusted fan-out value 
to construct the `RegionBindingsRef` object and initialize its field 
`BindingsLeft`.
3. `BindingsLeft` is decremented by the `RegionBindingsRef::add` method (which 
adds _a cluster_).
   - if  `BindingsLeft` is already zero, it's not decremented (so it won't 
overflow)
     - is this branch actually used?
     - if not, then it should be replaced by an assertion
   - there is an exceptional case where `removeSubRegionBindings` uses the 
3-argument form of the `add` method to avoid decrementing the `BindingsLeft` 
counter.
4. If `BindingsLeft == 1`, then `RegionBindingsRef::addBinding` hijacks the 
call and adds a default binding instead of the one that's specified by its 
argument.
5. If `BindingsLeft == 0`, then (as far as I see) early return branches with 
`if (B.hasExhaustedBindingLimit())` stop most (all?) code paths that would add 
bindings or clusters.
6. On these early return branches `escapeValue()` or `escapeValues()` calls 
ensure that the values that we ignored are marked as escaped.

 In this model there are three phases of the binding creation process:
 1. `BindingsLeft > 1`, bindings are created normally
 2. `BindingsLeft == 1`, intermediate state:
     - `add` works, can add clusters and can decrement `BindingsLeft` to zero
       - if `BindingsLeft` is decremented to zero here, then I don't think that 
the "add a default binding" step happens
     - `addBindings` adds a default binding (which decrements `BindingsLeft`) 
and records that the `SVal` specified in its argument has escaped 
 3. `BindingsLeft == 0`, exhausted state:
     - `hasExhaustedBindingLimit()` early returns prevent most (all?) addition 
of bindings
     -  but e.g. the three-argument `add` within `removeSubRegionBindings` 
still works
     - and perhaps there are some other code paths...
(I spent hours on trying to understand this, but I'm still not completely 
confident that I got every corner case right.) I didn't find anything that is 
outright buggy, but sometimes I felt that the code tries to preserve an 
invariant (e.g. "default binding is always added just before exhaustion"), but 
there is another branch where I don't see why is it preserved.

To simplify the picture a bit, I'd suggest the following alternative approach:
1. The constructor of `RegionManagerStore` always initializes 
`RegionStoreMaxBindingFanOut`  to `getOptions().RegionStoreMaxBindingFanOut` -- 
to avoid discrepancies and confusion between them.
2. If this fan-out value is nonzero, then 
`RegionManagerStore::getRegionBindings()` uses it directly to initialize 
`RegionBindingsRef::BindingsLeft` (i.e. it does not add one).
    - If the fan-out value is zero (which means unlimited), `BindingsLeft` may 
be initialized to `-1u`.
    - Alternatively, perhaps it would be more modern (but slightly more 
verbose) to say that `BindingsLeft` is an `std::optional<unsigned>` and it's 
initialized to `std:nullopt` when there is no limit (i.e. when using 
`getRegionBindingsWithUnboundedLimit` or when `RegionStoreMaxBindingFanOut == 
0`).
3. **(unchanged)** `BindingsLeft` is decremented by the 
`RegionBindingsRef::add` method (which adds _a cluster_).
   - if  `BindingsLeft` is already zero, it's not decremented (so it won't 
overflow)
     - is this branch actually used? -- if not, then it should be replaced by 
an assertion
   - there is an exceptional case where `removeSubRegionBindings` uses the 
3-argument form of the `add` method to avoid decrementing the `BindingsLeft` 
counter.
4. _There is no highjacking logic and `BindingsLeft == 1` has no special 
meaning._
5. **(unchanged)** If `BindingsLeft == 0`, then `B.hasExhaustedBindingLimit()` 
is true, which triggers early return on most (all?) code paths that would add 
bindings or clusters.
6. `escapeValue` and `escapeValues` are replaced by more general functions that
    - still mark the "leftover" values as escaped
    - but also add a default binding (with unknown value) to mark that the 
`RecordBindingsRef` stores incomplete information.

WDYT about this alternative architecture?

https://github.com/llvm/llvm-project/pull/127602
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to