-
Notifications
You must be signed in to change notification settings - Fork 14.8k
[analyzer] Suppress out of bounds reports after weak loop assumptions #109804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
23b2737
77fb227
8ae4b67
cdc73ab
e7e6ded
cbb46e5
e952f05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -121,6 +121,25 @@ struct EvalCallOptions { | |||||||||
EvalCallOptions() {} | ||||||||||
}; | ||||||||||
|
||||||||||
/// Simple control flow statements like `if` only produce a single state split, | ||||||||||
/// so the fact that they are included in the source code implies that both | ||||||||||
/// branches are possible (at least under some conditions) and the analyzer can | ||||||||||
/// freely assume either of them. (This is not entirely true, because there may | ||||||||||
/// be unmarked logical correlations between `if` statements, but is a good | ||||||||||
/// enough heuristic and the analyzer strongly relies on it.) | ||||||||||
/// On the other hand, in a loop the state may be split repeatedly at each | ||||||||||
/// evaluation of the loop condition, and this can lead to following "weak" | ||||||||||
/// assumptions even though the code does not imply that they're valid and the | ||||||||||
/// programmer intended to cover them. | ||||||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
/// This function is called to mark the `State` when the engine makes an | ||||||||||
/// assumption which is weak. Checkers may use this heuristical mark to discard | ||||||||||
/// result and reduce the amount of false positives. | ||||||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
ProgramStateRef recordWeakLoopAssumption(ProgramStateRef State); | ||||||||||
|
||||||||||
/// Returns true if `recordWeakLoopAssumption()` was called on the execution | ||||||||||
/// path which produced `State`. | ||||||||||
bool seenWeakLoopAssumption(ProgramStateRef State); | ||||||||||
Comment on lines
+146
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While browsing through the codebase, I've seen that these methods for the other traits are defined inside IIUC, they are in the global scope so that they can be accessed from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally I prefer plain functions instead of Making There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think these methods are strongly connected to this class, as they only make sense in the context of |
||||||||||
|
||||||||||
class ExprEngine { | ||||||||||
void anchor(); | ||||||||||
|
||||||||||
|
@@ -323,12 +342,13 @@ class ExprEngine { | |||||||||
|
||||||||||
/// ProcessBranch - Called by CoreEngine. Used to generate successor | ||||||||||
/// nodes by processing the 'effects' of a branch condition. | ||||||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
void processBranch(const Stmt *Condition, | ||||||||||
NodeBuilderContext& BuilderCtx, | ||||||||||
ExplodedNode *Pred, | ||||||||||
ExplodedNodeSet &Dst, | ||||||||||
const CFGBlock *DstT, | ||||||||||
const CFGBlock *DstF); | ||||||||||
/// If the branch condition is a loop condition, IterationsFinishedInLoop is | ||||||||||
/// the number of already finished iterations (0, 1, 2...); otherwise it's | ||||||||||
/// std::nullopt. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Can't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We could introduce it, but we'd need to maintain a nested stack of loops (1 iteration in that This would make the heuristic introduced in this commit slightly more accurate, but I think that for practical purposes the current code is already good enough, so I didn't invest time into implementing this loop iteration count stack. (However it would be a nice follow-up commit if I have time.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You can bind the current iteration number to the AST node of the loop, as it happens with nested I'd prefer using state traits here instead of modifying the engine API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not say that I looked at the
This difference means that in an array init loop the code needs to manage an iteration count, while here I think it's much better to access the canonical However, unfortunately the Notice that This is the reason why I spoke about a "stack" in my previous comment -- to properly reset the iteration count each time we "reach the loop again", we'd need:
In theory it's perfectly possible to implement this exact iteration counting, but I feel that in practice it's not worth the effort, because the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't binding the iteration count to the loop terminator sufficient? Let's say we pass the terminator to I'm not sure how well this would work though, since I didn't test it. |
||||||||||
void processBranch(const Stmt *Condition, NodeBuilderContext &BuilderCtx, | ||||||||||
ExplodedNode *Pred, ExplodedNodeSet &Dst, | ||||||||||
const CFGBlock *DstT, const CFGBlock *DstF, | ||||||||||
std::optional<unsigned> IterationsFinishedInLoop); | ||||||||||
|
||||||||||
/// Called by CoreEngine. | ||||||||||
/// Used to generate successor nodes for temporary destructors depending | ||||||||||
|
@@ -583,11 +603,11 @@ class ExprEngine { | |||||||||
ExplodedNode *Pred, | ||||||||||
ExplodedNodeSet &Dst); | ||||||||||
|
||||||||||
/// evalEagerlyAssumeBinOpBifurcation - Given the nodes in 'Src', eagerly assume symbolic | ||||||||||
/// expressions of the form 'x != 0' and generate new nodes (stored in Dst) | ||||||||||
/// with those assumptions. | ||||||||||
void evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, ExplodedNodeSet &Src, | ||||||||||
const Expr *Ex); | ||||||||||
/// evalEagerlyAssumeOpBifurcation - Given the nodes in 'Src', eagerly assume | ||||||||||
/// symbolic expressions of the form 'x != 0' or '!x' and generate new nodes | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't rename this method. The functionality of the method doesn't change, it still does what it used to do, so the renaming just creates noise in the diff. If you think the name is wrong, you can open a separate NFC to address that. Since more than one method would need to be renamed after this one, I suggest you leave this alone. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name of this method is incorrect and misleading, which wasted my time while I was debugging some aspect of my patch, so I want to rename it. I'm not interested in renaming other methods, and I think it's a big waste of time to create a separate commit for four lines of completely NFC changes. (Also, creating a separate patch for every small change is polluting the diff log with small inconsequential commits.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point was that some other methods that this method calls also have the same misleading and incorrect name like This is why I suggested a separate patch, or if you want to do it in this patch just keep them in 2 different commits so they are separated when the PR is merged. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you've already seen, I created a separate NFC PR for this renaming and a few other things: #112209 Once that's merged, I'll rebase this patch onto it. |
||||||||||
/// (stored in Dst) with those assumptions. | ||||||||||
void evalEagerlyAssumeOpBifurcation(ExplodedNodeSet &Dst, | ||||||||||
ExplodedNodeSet &Src, const Expr *Ex); | ||||||||||
|
||||||||||
static std::pair<const ProgramPointTag *, const ProgramPointTag *> | ||||||||||
geteagerlyAssumeBinOpBifurcationTags(); | ||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -212,6 +212,25 @@ typedef llvm::ImmutableMap<const LocationContext *, unsigned> | |||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingArrayDestruction, | ||||||
PendingArrayDestructionMap) | ||||||
|
||||||
// This trait is used to heuristically filter out results produced from | ||||||
// execution paths that took "weak" assumptions within a loop. | ||||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(SeenWeakLoopAssumption, bool) | ||||||
|
||||||
ProgramStateRef clang::ento::recordWeakLoopAssumption(ProgramStateRef State) { | ||||||
return State->set<SeenWeakLoopAssumption>(true); | ||||||
} | ||||||
|
||||||
bool clang::ento::seenWeakLoopAssumption(ProgramStateRef State) { | ||||||
return State->get<SeenWeakLoopAssumption>(); | ||||||
} | ||||||
Comment on lines
+219
to
+225
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every registered trait should be removed when they are no longer needed to stop polluting the states. Also, aren't the traits part of the exploded graph? If they are and they are not cleaned up, they pollute the egraph too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bit doesn't have any "no longer needed" point -- if we made some shaky assumption for a loop condition, I want to suppress all the ArrayBoundV2 reports that are reached through a path which takes that particular branch. (By the way, each There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does it still worth to keep them once we finished processing the loop? The loop condition only gives us information while we are inside the loop the condition belongs to, right? Once the loop is exited, the trait could be safely removed I think. I only see the pattern that every state trait that we have now comes with a method that sets it and another one that removes it, so I assume we remove them for a reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is nothing to be removed here as this trait is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, there is a common false positive pattern where an unfounded assumption within the loop is used after the loop and produces an unwanted result at that point. See e.g. the testcase There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but with this left turned on, the coverage drop is huge even in cases that are not affected by the assumption. void foo(int x, int y) {
for (unsigned i = 0; i < x; i++) ; // split the state and set SeenWeakLoopAssumption to 'true'
if (x != 0) return; // drop the 'true' branch
// no warnings are reported from this point on
int buf[1] = {0};
for (int i = 0; i < y; i++)
buf[i] = 1; // SeenWeakLoopAssumption is 'true', so the warning is suppressed
} This goes on through multiple function calls too. void a() {}
void b() { a(); }
void c() { b(); }
void d() { c(); }
void main() {
for (unsigned i = 0; i < x; i++) ;
if (x != 0) return;
// no warnings are reported from this point on
d();
} If a warning is found inside any of Since we generate a sink it is just 1 false negative though, while relying on an unfounded assumption might trigger a false positive in one of the nested functions, so I guess we can live with this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example void foo(int x, int y) {
for (unsigned i = 0; i < x; i++) ; // split the state and set SeenWeakLoopAssumption to 'true'
if (x != 0) return; // drop the 'true' branch
// no warnings are reported from this point on
} is a very good point and I'll probably add it to the tests to highlight this limitation of the heuristic. However, I've seen {{ArrayBoundV2}} reports where lots of stuff happens between the point where we assume that a loop can have 0 iterations (i.e. some length/size variable is equal to 0) and the point where this triggers an unwanted report; so I don't think that we can have a natural cutoff where the "SeenWeakLoopAssumption" bit may be safely cleared. I don't see a way to avoid these kinds of false negatives without a completely different loop handling approach, so I think we should accept them in the foreseeable future. (There are already lots of precedents for losing coverage after loops.)
Comment on lines
+219
to
+225
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please move these definitions to the place, where the other similar definitions for other state traits are found, so that they stay grouped together? |
||||||
|
||||||
// This trait points to the last expression (logical operator) where an eager | ||||||
// assumption introduced a state split (i.e. both cases were feasible). This is | ||||||
// used by the WeakLoopAssumption heuristic to find situations where the an | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// eager assumption introduces a state split within the evaluation of a loop | ||||||
// condition. | ||||||
REGISTER_TRAIT_WITH_PROGRAMSTATE(LastEagerlyAssumeAssumptionAt, const Expr *) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to emphasize that I'm only marking the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggested a renaming only because I'm not sure that |
||||||
|
||||||
//===----------------------------------------------------------------------===// | ||||||
// Engine construction and deletion. | ||||||
//===----------------------------------------------------------------------===// | ||||||
|
@@ -2128,7 +2147,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, | |||||
(B->isRelationalOp() || B->isEqualityOp())) { | ||||||
ExplodedNodeSet Tmp; | ||||||
VisitBinaryOperator(cast<BinaryOperator>(S), Pred, Tmp); | ||||||
evalEagerlyAssumeBinOpBifurcation(Dst, Tmp, cast<Expr>(S)); | ||||||
evalEagerlyAssumeOpBifurcation(Dst, Tmp, cast<Expr>(S)); | ||||||
} | ||||||
else | ||||||
VisitBinaryOperator(cast<BinaryOperator>(S), Pred, Dst); | ||||||
|
@@ -2401,7 +2420,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, | |||||
if (AMgr.options.ShouldEagerlyAssume && (U->getOpcode() == UO_LNot)) { | ||||||
ExplodedNodeSet Tmp; | ||||||
VisitUnaryOperator(U, Pred, Tmp); | ||||||
evalEagerlyAssumeBinOpBifurcation(Dst, Tmp, U); | ||||||
evalEagerlyAssumeOpBifurcation(Dst, Tmp, U); | ||||||
} | ||||||
else | ||||||
VisitUnaryOperator(U, Pred, Dst); | ||||||
|
@@ -2761,12 +2780,10 @@ assumeCondition(const Stmt *Condition, ExplodedNode *N) { | |||||
return State->assume(V); | ||||||
} | ||||||
|
||||||
void ExprEngine::processBranch(const Stmt *Condition, | ||||||
NodeBuilderContext& BldCtx, | ||||||
ExplodedNode *Pred, | ||||||
ExplodedNodeSet &Dst, | ||||||
const CFGBlock *DstT, | ||||||
const CFGBlock *DstF) { | ||||||
void ExprEngine::processBranch( | ||||||
const Stmt *Condition, NodeBuilderContext &BldCtx, ExplodedNode *Pred, | ||||||
ExplodedNodeSet &Dst, const CFGBlock *DstT, const CFGBlock *DstF, | ||||||
std::optional<unsigned> IterationsFinishedInLoop) { | ||||||
assert((!Condition || !isa<CXXBindTemporaryExpr>(Condition)) && | ||||||
"CXXBindTemporaryExprs are handled by processBindTemporary."); | ||||||
const LocationContext *LCtx = Pred->getLocationContext(); | ||||||
|
@@ -2808,27 +2825,63 @@ void ExprEngine::processBranch(const Stmt *Condition, | |||||
std::tie(StTrue, StFalse) = *KnownCondValueAssumption; | ||||||
else { | ||||||
assert(!isa<ObjCForCollectionStmt>(Condition)); | ||||||
// TODO: instead of this shortcut perhaps it would be better to "rejoin" | ||||||
// the common execution path with | ||||||
// StTrue = StFalse = PrevState; | ||||||
steakhal marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
builder.generateNode(PrevState, true, PredN); | ||||||
builder.generateNode(PrevState, false, PredN); | ||||||
continue; | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
if (StTrue && StFalse) | ||||||
assert(!isa<ObjCForCollectionStmt>(Condition)); | ||||||
|
||||||
const Expr *EagerlyAssumeExpr = | ||||||
PrevState->get<LastEagerlyAssumeAssumptionAt>(); | ||||||
const Expr *ConditionExpr = dyn_cast<Expr>(Condition); | ||||||
if (ConditionExpr) | ||||||
ConditionExpr = ConditionExpr->IgnoreParenCasts(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why skip casts? Also if the condition is an expression, removing the parenthesis has already happened by this point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm skipping casts to ensure consistent behavior between the Perhaps this question warrants more investigation, I'll think about it and maybe add a few new testcases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I summarized my thoughts in the inline comment #109804 (comment) (which is attached to the test which corresponds to this part of the code). |
||||||
bool DidEagerlyAssume = EagerlyAssumeExpr == ConditionExpr; | ||||||
bool BothFeasible = (DidEagerlyAssume || (StTrue && StFalse)) && | ||||||
builder.isFeasible(true) && builder.isFeasible(false); | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
// Process the true branch. | ||||||
if (builder.isFeasible(true)) { | ||||||
if (StTrue) | ||||||
if (StTrue) { | ||||||
if (BothFeasible && IterationsFinishedInLoop && | ||||||
*IterationsFinishedInLoop >= 2) { | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// When programmers write a loop, they imply that at least two | ||||||
// iterations are possible (otherwise they would just write an `if`), | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// but the third iteration is not implied: there are situations where | ||||||
// the programmer knows that there won't be a third iteration (e.g. | ||||||
// they iterate over a structure that has <= 2 elements) but this is | ||||||
// not marked in the source code. | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// Checkers may use this heuristic mark to discard results found on | ||||||
// branches that contain this "weak" assumption. | ||||||
StTrue = recordWeakLoopAssumption(StTrue); | ||||||
} | ||||||
builder.generateNode(StTrue, true, PredN); | ||||||
else | ||||||
} else { | ||||||
builder.markInfeasible(true); | ||||||
} | ||||||
} | ||||||
|
||||||
// Process the false branch. | ||||||
if (builder.isFeasible(false)) { | ||||||
if (StFalse) | ||||||
if (StFalse) { | ||||||
if (BothFeasible && IterationsFinishedInLoop && | ||||||
*IterationsFinishedInLoop == 0) { | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// There are many situations where the programmers know that there | ||||||
// will be at least one iteration in a loop (e.g. a structure is not | ||||||
// empty) but the analyzer cannot deduce this and reports false | ||||||
// positives after skipping the loop. | ||||||
// Checkers may use this heuristic mark to discard results found on | ||||||
// branches that contain this "weak" assumption. | ||||||
StFalse = recordWeakLoopAssumption(StFalse); | ||||||
} | ||||||
builder.generateNode(StFalse, false, PredN); | ||||||
else | ||||||
} else { | ||||||
builder.markInfeasible(false); | ||||||
} | ||||||
} | ||||||
} | ||||||
currBldrCtx = nullptr; | ||||||
|
@@ -3752,9 +3805,9 @@ ExprEngine::geteagerlyAssumeBinOpBifurcationTags() { | |||||
&eagerlyAssumeBinOpBifurcationFalse); | ||||||
} | ||||||
|
||||||
void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, | ||||||
ExplodedNodeSet &Src, | ||||||
const Expr *Ex) { | ||||||
void ExprEngine::evalEagerlyAssumeOpBifurcation(ExplodedNodeSet &Dst, | ||||||
ExplodedNodeSet &Src, | ||||||
const Expr *Ex) { | ||||||
StmtNodeBuilder Bldr(Src, Dst, *currBldrCtx); | ||||||
|
||||||
for (const auto Pred : Src) { | ||||||
|
@@ -3776,6 +3829,11 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, | |||||
ProgramStateRef StateTrue, StateFalse; | ||||||
std::tie(StateTrue, StateFalse) = state->assume(*SEV); | ||||||
|
||||||
if (StateTrue && StateFalse) { | ||||||
StateTrue = StateTrue->set<LastEagerlyAssumeAssumptionAt>(Ex); | ||||||
StateFalse = StateFalse->set<LastEagerlyAssumeAssumptionAt>(Ex); | ||||||
} | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
// First assume that the condition is true. | ||||||
if (StateTrue) { | ||||||
SVal Val = svalBuilder.makeIntVal(1U, Ex->getType()); | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,4 +1,9 @@ | ||||||
// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -verify %s | ||||||
// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -analyzer-config eagerly-assume=false -verify %s | ||||||
|
||||||
// Note that eagerly-assume=false is tested separately because the | ||||||
// WeakLoopAssumption suppression heuristic uses different code paths to | ||||||
// achieve the same result with and without eagerly-assume. | ||||||
|
||||||
void clang_analyzer_eval(int); | ||||||
|
||||||
|
@@ -194,3 +199,99 @@ char test_comparison_with_extent_symbol(struct incomplete *p) { | |||||
return ((char *)p)[-1]; // no-warning | ||||||
} | ||||||
|
||||||
// WeakLoopAssumption suppression | ||||||
/////////////////////////////////////////////////////////////////////// | ||||||
|
||||||
int GlobalArray[100]; | ||||||
int loop_suppress_after_zero_iterations(unsigned len) { | ||||||
for (unsigned i = 0; i < len; i++) | ||||||
if (GlobalArray[i] > 0) | ||||||
return GlobalArray[i]; | ||||||
// Previously this would have produced an overflow warning because splitting | ||||||
// the state on the loop condition introduced an execution path where the | ||||||
// analyzer thinks that len == 0. | ||||||
// There are very many situations where the programmer knows that an argument | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to keep the "very" here -- that's the impression that I get when I browse through lots of |
||||||
// is positive, but this is not indicated in the source code, so we must | ||||||
// avoid reporting errors (especially out of bounds errors) on these | ||||||
// branches, because otherwise we'd get prohibitively many false positives. | ||||||
return GlobalArray[len - 1]; // no-warning | ||||||
} | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
void loop_report_in_second_iteration(int len) { | ||||||
int buf[1] = {0}; | ||||||
for (int i = 0; i < len; i++) { | ||||||
// When a programmer writes a loop, we may assume that they intended at | ||||||
// least two iterations. | ||||||
buf[i] = 1; // expected-warning{{Out of bound access to memory}} | ||||||
} | ||||||
} | ||||||
|
||||||
void loop_suppress_in_third_iteration(int len) { | ||||||
int buf[2] = {0}; | ||||||
for (int i = 0; i < len; i++) { | ||||||
// We should suppress array bounds errors on the third and later iterations | ||||||
// of loops, because sometimes programmers write a loop in sitiuations | ||||||
// where they know that there will be at most two iterations. | ||||||
buf[i] = 1; // no-warning | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
} | ||||||
|
||||||
void loop_suppress_in_third_iteration_cast(int len) { | ||||||
int buf[2] = {0}; | ||||||
for (int i = 0; (unsigned)(i < len); i++) { | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// Check that a (somewhat arbitrary) cast does not hinder the recognition | ||||||
// of the condition expression. | ||||||
buf[i] = 1; // no-warning | ||||||
} | ||||||
} | ||||||
|
||||||
void loop_suppress_in_third_iteration_logical_and(int len, int flag) { | ||||||
int buf[2] = {0}; | ||||||
for (int i = 0; i < len && flag; i++) { | ||||||
// FIXME: In this case the checker should suppress the warning the same way | ||||||
// as it's suppressed in loop_suppress_in_third_iteration, but the | ||||||
// suppression is not activated because the terminator statement associated | ||||||
// with the loop is just the expression 'flag', while 'i < len' is a | ||||||
// separate terminator statement that's associated with the | ||||||
// short-circuiting operator '&&'. | ||||||
// I have seen a real-world FP that looks like this, but it is much rarer | ||||||
// than the basic setup. | ||||||
buf[i] = 1; // expected-warning{{Out of bound access to memory}} | ||||||
} | ||||||
} | ||||||
|
||||||
void loop_suppress_in_third_iteration_logical_and_2(int len, int flag) { | ||||||
int buf[2] = {0}; | ||||||
for (int i = 0; flag && i < len; i++) { | ||||||
// If the two operands of '&&' are flipped, the suppression works. | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
buf[i] = 1; // no-warning | ||||||
} | ||||||
} | ||||||
|
||||||
int coinflip(void); | ||||||
int do_while_report_after_one_iteration(void) { | ||||||
int i = 0; | ||||||
do { | ||||||
i++; | ||||||
} while (coinflip()); | ||||||
// Unlike `loop_suppress_after_zero_iterations`, running just one iteration | ||||||
// in a do-while is not a corner case that would produce too many false | ||||||
// positives, so don't suppress bounds errors in these situations. | ||||||
return GlobalArray[i-2]; // expected-warning{{Out of bound access to memory}} | ||||||
} | ||||||
|
||||||
void do_while_report_in_second_iteration(int len) { | ||||||
int buf[1] = {0}; | ||||||
int i = 0; | ||||||
do { | ||||||
buf[i] = 1; // expected-warning{{Out of bound access to memory}} | ||||||
} while (i++ < len); | ||||||
} | ||||||
|
||||||
void do_while_suppress_in_third_iteration(int len) { | ||||||
int buf[2] = {0}; | ||||||
int i = 0; | ||||||
do { | ||||||
buf[i] = 1; // no-warning | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} while (i++ < len); | ||||||
} | ||||||
isuckatcs marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.