[PATCH] D41938: [Analyzer] SValBuilder Comparison Rearrangement (with Restrictions and Analyzer Option)
George Karpenkov via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 18 13:17:06 PST 2018
george.karpenkov added inline comments.
================
Comment at: include/clang/StaticAnalyzer/Core/AnalyzerOptions.h:597
+ // than or equal to the quarter of the maximum value of that type.
+ bool shouldAggressivelySimplifyRelationalComparison();
+
----------------
High level comment: can you list all [[ https://en.wikipedia.org/wiki/Rewriting | rewriting ]] rules you are applying in a formula explicitly in a comment?
>From the phabricator discussion I see that you are applying
```
A + n >= B + m -> A - B >= n + m // A, B symbolic, n and m are integers
```
but from the code it seems that you are applying more canonicalization rules?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:317
+// with simpler symbols on every recursive call.
+static bool isOverflowClampedBy4(SymbolRef Sym, ProgramStateRef State) {
+ SValBuilder &SVB = State->getStateManager().getSValBuilder();
----------------
Can we have a better function name here? I can't think of one straight away, but "clamped" is not very self-descriptive. `isWithinConstantOverflowBounds`?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:326
+
+ llvm::APSInt Max = AT.getMaxValue() >> 2; // Divide by 4.
+ SVal IsCappedFromAbove =
----------------
Would just division produce the same result? Also probably it's better to make "4" a constant, at least with `#define`
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:330
+ nonloc::ConcreteInt(Max), SVB.getConditionType());
+ if (auto DV = IsCappedFromAbove.getAs<DefinedSVal>()) {
+ if (State->assume(*DV, false))
----------------
6 lines of branching is probably better expressed as
```
if (!isa<DefinedSVal>(IsCappedFromAbove) || State->assume(*dyn_cast<DefinedSVal>(IsCappedFromAbove), false))
return false
```
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:337
+
+ llvm::APSInt Min = -Max;
+ SVal IsCappedFromBelow =
----------------
326-335 duplicates 338-346.
Perhaps we could have
```
static bool isCappedFrom(bool Above, llvm::APSInt bound, ...)
```
?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:352
+// Same for the concrete integers: see if I is within [min/4, max/4].
+static bool isOverflowClampedBy4(llvm::APSInt I) {
+ APSIntType AT(I);
----------------
Again, could we do better with a function name?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:357
+
+ llvm::APSInt Max = AT.getMaxValue() >> 2;
+ if (I > Max)
----------------
I think you want
```
return (I <= Max) && (I >= -Max)
```
instead of 358-365
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:376
+ // Fail to decompose: "reduce" the problem to the "$x + 0" case.
+ return std::make_tuple(Sym, BO_Add, BV.getValue(0, Sym->getType()));
+}
----------------
Is it beneficial to do this though? At the point where `decomposeSymbol` is called we are still checking whether the rearrangement could be performed, so maybe just returning a false flag would be better?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:403
+ if (LOp == BO_Sub)
+ LInt = -LInt;
+ else
----------------
I think this is a separate rewrite, which is better performed in a separate function.
If you move it to `decomposeSymbol` or to a separate function, you would get a lot of simplifications:
1. This function would be performing only a single rewrite.
2. You would no longer need to take `Lop` and `Rop` as parameters
3. At that point, two separate cases would be clearly seen: `Op` is a comparison operator, or `Op` is an additive operator.
I would separate those two rewrites in separate functions, distinguishing between them at the callsite.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:429
+ if (BinaryOperator::isComparisonOp(Op)) {
+ // Prefer comparing to a non-negative number.
+ // FIXME: Maybe it'd be better to have consistency in
----------------
It seems that having a concrete positive RHS is a part of your rewrite rule. In that case, that should be documented in a high-level overview of the rewrite rules.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:432
+ // "$x - $y" vs. "$y - $x" because those are solver's keys.
+ if (LInt > RInt) {
+ ResultSym = SymMgr.getSymSymExpr(RSym, BO_Sub, LSym, SymTy);
----------------
I think this could be shortened and made more explicit by constructing the LHS and RHS first, and then reversing both and the comparison operator if RHS is negative.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:472
+ if (BinaryOperator::isComparisonOp(Op) &&
+ Opts.shouldAggressivelySimplifyRelationalComparison()) {
+ if (ResultTy != SVB.getConditionType())
----------------
I am confused why the option `shouldAggressivelySimplifyRelationalComparison` is checked here. Do we rewrite cases where `Op` is additive even if the option is not checked? It's generally better to check the flag outside of the rearranging function, so that the high-level logic can be seen.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:489
+ if (SingleTy != ResultTy)
+ return None;
+ } else {
----------------
This check is weird: you have just set `SingleTy` to `ResultTy` at line 477.
Instead of this whole if-block (from line 487), why not just set `SingleTy` above line 473. where you have already checked that the operator is performing comparison?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:494
+
+ assert(!SingleTy.isNull() && "We should have figured out the type by now!");
+
----------------
I would also move this assert up, below line 481.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:497
+ // Substracting unsigned integers is a nightmare.
+ if (!SingleTy->isSignedIntegerOrEnumerationType())
+ return None;
----------------
this check should be inside the block at line 477, since it does not make sense for relational comparisons.
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:508
+ std::tie(LSym, LOp, LInt) = decomposeSymbol(LSym, BV);
+ if (LSym->getType() != SingleTy ||
+ !isOverflowClampedBy4(LSym, State) || !isOverflowClampedBy4(LInt))
----------------
Why `isComparisonOp()` check is done in 512-516, but not here? Additionally, can this check be factored out into a function?
================
Comment at: lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp:774
+ if (Optional<NonLoc> V = tryRearrange(state, op, lhs, rhs, resultTy))
+ return *V;
----------------
I would expect that checking the analyzer option would be performed here?
================
Comment at: test/Analysis/svalbuilder-rearrange-comparisons.c:19
+ if (x < -(((int)INT_MAX) / 4))
+ exit(1);
+ return x;
----------------
assert would express intent better
================
Comment at: test/Analysis/svalbuilder-rearrange-comparisons.c:28
+ clang_analyzer_dump(x == y);
+ // expected-warning at -1{{((conj_$2{int}) - (conj_$5{int})) == 0}}
+}
----------------
Can we also have integration-like tests where rearrangement would affect observable behavior?
Also, `clang_analyzer_dump` is a debugging function, and I am not sure whether we should rely on its output in tests.
https://reviews.llvm.org/D41938
More information about the cfe-commits
mailing list