Skip to content
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

C++: failing test for a weird range analysis #7396

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

@redsun82
Copy link
Contributor

@redsun82 redsun82 commented Dec 14, 2021

Bounded variables (once by a for loop, then even more bounded by an if) seem to throw off range analysis if a subtraction (even with 0) is done on them.

Notice that any of:

  • not having the outer for loop (even replacing it with an if making the same bounds), or
  • not subtracting

will not show the false positive.

@redsun82 redsun82 requested a review from geoffw0 Dec 14, 2021
@github-actions github-actions bot added the C++ label Dec 14, 2021
Bounded variables (once by a for loop, then even more bounded by an if)
seem to throw off range analysis if a subtraction (even with 0) is done
on them.

Notice that any of:
* not having the outer for loop (even replacing it with an if making the
  same bounds), or
* not subtracting

will not show the false positive.
if (1 <= i && i <= 10) {
sprintf(buffer, "%d", i - 1); // GOOD, 0 <= i - 1 <= 9
}
Copy link
Contributor

@geoffw0 geoffw0 Dec 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what the problem is. Did you mean to show a false positive case?

Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this is a weird FP. What seems weird to me is that i - 0 has a different range being found out than i alone (removing - 0 makes range analysis find the correct range here), or that the analysis works correctly also if we do

void test7(int i) {
  char buffer[2];
  if (0 <= i && i <= 11) {
    if (i <= 9) {
      sprintf(...);

Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the current test results together with the equivalent thing in library tests, I'll comment there

if (1 <= i && i <= 10) {
sprintf(buffer, "%d", i - 1); // GOOD, 0 <= i - 1 <= 9
}
Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the current test results together with the equivalent thing in library tests, I'll comment there

| weird.c:5:15:5:15 | i | 127 |
| weird.c:5:26:5:26 | i | 15 |
| weird.c:6:14:6:14 | i | 11 |
| weird.c:6:19:6:19 | i | 15 |
| weird.c:7:11:7:11 | i | 9 |
| weird.c:9:7:9:7 | j | 2147483647 |
| weird.c:9:11:9:11 | i | 9 |
| weird.c:10:11:10:11 | j | 15 |
Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's what puzzles me: although i gets the correct upper bound of 9 on weird.c:9, and j is assigned i - 0, j actually gets an upper bound of 15.

I also have no idea where the 127 and 15 bounds for i come from in line 5 (for loop) and line 6 (if). Also notice that there is nothing similar going on on the lower bounds.

Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uh, I just noticed in test.c

void widen_recursive_expr() {
  int s;
  for (s = 0; s < 10; s++) {
    int result = s + s; // 0 .. 9 [BUG: upper bound is 15 due to widening]
    out(result); // 0 .. 18 [BUG: upper bound is 127 due to double widening]
  }
}

is this mentioning exactly what is going on here?

Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the widening explanation on SimpleRangeAnalysis.qll, which at least explains to me where the 15 and 127 bounds come from (and also why I see no such thing on the lower bounds, it's just that 0 is in the finite set of approximations of lower bounds).

Still, I feel that limitation should not really apply here: j = i - 0 comes after a spot where the correct bounds for i have been found, so why does it fall back to the widening finite set of approximations for j?

Copy link
Contributor

@MathiasVP MathiasVP Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still, I feel that limitation should not really apply here: j = i - 0 comes after a spot where the correct bounds for i have been found, so why does it fall back to the widening finite set of approximations for j?

This is, unfortunately, the expected behavior from the library. I'll explain the reason below. Let me paste in the problematic snippet here to aid the explanation:

void weird1() {
  int i;
  for (i = 0; i <= 11; ++i) {
    if (0 <= i && i <= 9) {
      out(i);
      int j;
      j = i - 0;
      out(j);
    }
  } 
}

The TLDR is that we deduce the bound [0, 15] for j because the assignment to j depends on the recursively defined variable i (i is recursive because of the ++i in the loop header). The long version is the following:

  1. The main loop of the range-analysis recursion (getUpperBoundsImpl) delegates to getDefUpperBounds for a definitions like j = i - 0.
  2. getDefUpperBounds delegates to getDefUpperBoundsImpl.
  3. getDefUpperBoundsImpl uses assignmentDef to deduce that the right-hand-side of j = i - 0 is i - 0, and we delegate to getFullyConvertedUpperBounds to deduce an upper bound for i - 0.
  4. getFullyConvertedUpperBounds calls getTruncatedUpperBounds to factor in implicit conversions on i - 0 (which aren't relevant in this case).
  5. getTruncatedUpperBounds calls getUpperBoundsImpl to get an upper bound for i - 0.
  6. getUpperBoundsImpl enters the case for SubExpr, which calls getFullyConvertedUpperBounds on the left-hand side (i)
  7. getFullyConvertedUpperBounds calls getTruncatedUpperBounds to factor in implicit conversions on i (again, which aren't relevant in this case).
  8. getTruncatedUpperBounds calls getUpperBoundsImpl to get an upper bound on i
  9. getUpperBoundsImpl enters the SSA case, and calls getDefUpperBounds with the definition for i which in this case is ++i. This is a recursive definition, and so the upper bound for i is widened to 15.

Copy link
Contributor Author

@redsun82 redsun82 Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need to wrap my head around it a bit, but I can already ask this: why is i on line 7 not affected by this (or the i access on in the assignment to j as well)? The way I see it, there is the approximated, widened bound given by the loop, but then there is a stricter bound imposed by the if. How it is that this stricter bound is applied on a direct access to i, but not on an expression containing i?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

3 participants