Skip to content

[NFC][analyzer] OOB test consolidation III: 'outofbound' tests #128508

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

Merged
merged 1 commit into from
Feb 28, 2025

Conversation

NagyDonat
Copy link
Contributor

Before commit 6e17ed9 the test files outofbound.c and outofbound-notwork.c tested the behavior of the old alpha checker alpha.security.ArrayBound (V1); then that commit converted them into tests for the checker security.ArrayBound which was previously called alpha.security.ArrayBoundV2.

This commit removes these test files and migrates their useful content to out-of-bounds.c. The file outofbound.c contained lots of testcases that covered features which are also covered in out-of-bounds.c or out-of-bounds-diagnostics.c; those redundant cases are discarded during this migration process.

This is part of a commit series that reorganizes the tests of security.ArrayBound to system that's easier to understand and maintain.

Before commit 6e17ed9 the test files `outofbound.c` and
`outofbound-notwork.c` tested the behavior of the old alpha checker
`alpha.security.ArrayBound` (V1); then that commit converted them into
tests for the checker `security.ArrayBound` which was previously called
`alpha.security.ArrayBoundV2`.

This commit removes these test files and migrates their useful content
to `out-of-bounds.c`. The file `outofbound.c` contained lots of
testcases that covered features which are also covered in
`out-of-bounds.c` or `out-of-bounds-diagnostics.c`; those redundant
cases are discarded during this migration process.

This is part of a commit series that reorganizes the tests of
`security.ArrayBound` to system that's easier to understand and
maintain.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Feb 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 24, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Donát Nagy (NagyDonat)

Changes

Before commit 6e17ed9 the test files outofbound.c and outofbound-notwork.c tested the behavior of the old alpha checker alpha.security.ArrayBound (V1); then that commit converted them into tests for the checker security.ArrayBound which was previously called alpha.security.ArrayBoundV2.

This commit removes these test files and migrates their useful content to out-of-bounds.c. The file outofbound.c contained lots of testcases that covered features which are also covered in out-of-bounds.c or out-of-bounds-diagnostics.c; those redundant cases are discarded during this migration process.

This is part of a commit series that reorganizes the tests of security.ArrayBound to system that's easier to understand and maintain.


Full diff: https://github.com/llvm/llvm-project/pull/128508.diff

3 Files Affected:

  • (modified) clang/test/Analysis/out-of-bounds.c (+50)
  • (removed) clang/test/Analysis/outofbound-notwork.c (-32)
  • (removed) clang/test/Analysis/outofbound.c (-130)
diff --git a/clang/test/Analysis/out-of-bounds.c b/clang/test/Analysis/out-of-bounds.c
index 7d6cb4ecf1b24..2174dafc0021b 100644
--- a/clang/test/Analysis/out-of-bounds.c
+++ b/clang/test/Analysis/out-of-bounds.c
@@ -217,3 +217,53 @@ int test_negative_offset_with_unsigned_idx(void) {
   unsigned idx = 2u;
   return p[idx]; // expected-warning {{Out of bound access to memory preceding}}
 }
+
+struct three_words { int c[3]; };
+struct seven_words { int c[7]; };
+void partially_in_bounds(void) {
+  struct seven_words c;
+  struct three_words a, *p = (struct three_words *)&c;
+  p[0] = a; // no-warning
+  p[1] = a; // no-warning
+  p[2] = a; // should warn
+  // FIXME: This is an overflow, but currently security.ArrayBound only checks
+  // that the _beginning_ of the accessed element is within bounds.
+}
+
+void vla(int a) {
+  if (a == 5) {
+    int x[a];
+    x[4] = 4; // no-warning
+    x[5] = 5; // expected-warning{{Out of bound access}}
+  }
+}
+
+void sizeof_vla(int a) {
+  // FIXME: VLA modeling is not good enough to cover this case.
+  if (a == 5) {
+    char x[a];
+    int y[sizeof(x)];
+    y[4] = 4; // no-warning
+    y[5] = 5; // should be {{Out of bounds access}}
+  }
+}
+
+void sizeof_vla_2(int a) {
+  // FIXME: VLA modeling is not good enough to cover this case.
+  if (a == 5) {
+    char x[a];
+    int y[sizeof(x) / sizeof(char)];
+    y[4] = 4; // no-warning
+    y[5] = 5; // should be {{Out of bounds access}}
+  }
+}
+
+void sizeof_vla_3(int a) {
+  // FIXME: VLA modeling is not good enough to cover this case.
+  if (a == 5) {
+    char x[a];
+    int y[sizeof(*&*&*&x)];
+    y[4] = 4; // no-warning
+    y[5] = 5; // should be {{Out of bounds access}}
+  }
+}
diff --git a/clang/test/Analysis/outofbound-notwork.c b/clang/test/Analysis/outofbound-notwork.c
deleted file mode 100644
index 1318c07bbf2a8..0000000000000
--- a/clang/test/Analysis/outofbound-notwork.c
+++ /dev/null
@@ -1,32 +0,0 @@
-// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound -verify %s
-// XFAIL: *
-
-// Once we better handle modeling of sizes of VLAs, we can pull this back
-// into outofbound.c.
-
-void sizeof_vla(int a) {
-  if (a == 5) {
-    char x[a];
-    int y[sizeof(x)];
-    y[4] = 4; // no-warning
-    y[5] = 5; // expected-warning{{Out of bounds access}}
-  }
-}
-
-void sizeof_vla_2(int a) {
-  if (a == 5) {
-    char x[a];
-    int y[sizeof(x) / sizeof(char)];
-    y[4] = 4; // no-warning
-    y[5] = 5; // expected-warning{{Out of bounds access}}
-  }
-}
-
-void sizeof_vla_3(int a) {
-  if (a == 5) {
-    char x[a];
-    int y[sizeof(*&*&*&x)];
-    y[4] = 4; // no-warning
-    y[5] = 5; // expected-warning{{Out of bounds access}}
-  }
-}
diff --git a/clang/test/Analysis/outofbound.c b/clang/test/Analysis/outofbound.c
deleted file mode 100644
index d3d8ff2b2f0ed..0000000000000
--- a/clang/test/Analysis/outofbound.c
+++ /dev/null
@@ -1,130 +0,0 @@
-// RUN: %clang_analyze_cc1 -Wno-array-bounds -verify %s \
-// RUN:   -analyzer-checker=core \
-// RUN:   -analyzer-checker=unix \
-// RUN:   -analyzer-checker=security.ArrayBound \
-// RUN:   -analyzer-config unix.DynamicMemoryModeling:Optimistic=true
-
-typedef __typeof(sizeof(int)) size_t;
-void *malloc(size_t);
-void *calloc(size_t, size_t);
-
-char f1(void) {
-  char* s = "abcd";
-  char c = s[4]; // no-warning
-  return s[5] + c; // expected-warning{{Out of bound access to memory after}}
-}
-
-void f2(void) {
-  int *p = malloc(12);
-  p[3] = 4; // expected-warning{{Out of bound access to memory after}}
-}
-
-struct three_words {
-  int c[3];
-};
-
-struct seven_words {
-  int c[7];
-};
-
-void f3(void) {
-  struct three_words a, *p;
-  p = &a;
-  p[0] = a; // no-warning
-  p[1] = a; // expected-warning{{Out of bound access to memory after}}
-}
-
-void f4(void) {
-  struct seven_words c;
-  struct three_words a, *p = (struct three_words *)&c;
-  p[0] = a; // no-warning
-  p[1] = a; // no-warning
-  p[2] = a; // should warn
-  // FIXME: This is an overflow, but currently security.ArrayBound only checks
-  // that the _beginning_ of the accessed element is within bounds.
-}
-
-void f5(void) {
-  char *p = calloc(2,2);
-  p[3] = '.'; // no-warning
-  p[4] = '!'; // expected-warning{{Out of bound access}}
-}
-
-void f6(void) {
-  char a[2];
-  int *b = (int*)a;
-  b[1] = 3; // expected-warning{{Out of bound access}}
-}
-
-void f7(void) {
-  struct three_words a;
-  a.c[3] = 1; // expected-warning{{Out of bound access}}
-}
-
-void vla(int a) {
-  if (a == 5) {
-    int x[a];
-    x[4] = 4; // no-warning
-    x[5] = 5; // expected-warning{{Out of bound access}}
-  }
-}
-
-void alloca_region(int a) {
-  if (a == 5) {
-    char *x = __builtin_alloca(a);
-    x[4] = 4; // no-warning
-    x[5] = 5; // expected-warning{{Out of bound access}}
-  }
-}
-
-int symbolic_index(int a) {
-  int x[2] = {1, 2};
-  if (a == 2) {
-    return x[a]; // expected-warning{{Out of bound access}}
-  }
-  return 0;
-}
-
-int symbolic_index2(int a) {
-  int x[2] = {1, 2};
-  if (a < 0) {
-    return x[a]; // expected-warning{{Out of bound access}}
-  }
-  return 0;
-}
-
-int overflow_binary_search(double in) {
-  int eee = 16;
-  if (in < 1e-8 || in > 1e23) {
-    return 0;
-  } else {
-    static const double ins[] = {1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1,
-                                 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
-                                 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
-                                 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
-    if (in < ins[eee]) {
-      eee -= 8;
-    } else {
-      eee += 8;
-    }
-    if (in < ins[eee]) {
-      eee -= 4;
-    } else {
-      eee += 4;
-    }
-    if (in < ins[eee]) {
-      eee -= 2;
-    } else {
-      eee += 2;
-    }
-    if (in < ins[eee]) {
-      eee -= 1;
-    } else {
-      eee += 1;
-    }
-    if (in < ins[eee]) { // expected-warning {{Out of bound access}}
-      eee -= 1;
-    }
-  }
-  return eee;
-}

Copy link
Contributor Author

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

I annotated outofbound.c with inline comments to describe why I removed most of the testcases within it.

Comment on lines -11 to -15
char f1(void) {
char* s = "abcd";
char c = s[4]; // no-warning
return s[5] + c; // expected-warning{{Out of bound access to memory after}}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK this test won't have a direct equivalent after this commit; but they are tested incidentally by a few (intFromString and intFromStringDivisible) in out-of-bounds-diagnostics.c. String literals are simple and straightforward regions, so I think this coverage is sufficient for them; but I could migrate this test case if you wish.

Comment on lines -17 to -20
void f2(void) {
int *p = malloc(12);
p[3] = 4; // expected-warning{{Out of bound access to memory after}}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

malloc is tested at mallocRegion in out-of-bounds-diagnostics.c (without unwarranted assumptions about sizeof(int)).

Comment on lines -30 to -35
void f3(void) {
struct three_words a, *p;
p = &a;
p[0] = a; // no-warning
p[1] = a; // expected-warning{{Out of bound access to memory after}}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is very close to scalarOverflow in out-of-bounds-diagnostics.c. The only difference is that this uses a struct instead of an int, which is not relevant for the logic of the checker (neither is special cased, and there are lots of cases verifying that structs work properly), so I'm discarding this.

Comment on lines -37 to -45
void f4(void) {
struct seven_words c;
struct three_words a, *p = (struct three_words *)&c;
p[0] = a; // no-warning
p[1] = a; // no-warning
p[2] = a; // should warn
// FIXME: This is an overflow, but currently security.ArrayBound only checks
// that the _beginning_ of the accessed element is within bounds.
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm migrating this testcase to out-of-bounds.c under the more descriptive name partially_in_bounds.

Comment on lines -47 to -51
void f5(void) {
char *p = calloc(2,2);
p[3] = '.'; // no-warning
p[4] = '!'; // expected-warning{{Out of bound access}}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the POV of the ArrayBound checker calloc is identical to malloc. (Any differences between them should be tested in tests for MallocChecker.)

Comment on lines -64 to -70
void vla(int a) {
if (a == 5) {
int x[a];
x[4] = 4; // no-warning
x[5] = 5; // expected-warning{{Out of bound access}}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Migrating this to out-of-bounds.c.

Comment on lines -71 to -78

void alloca_region(int a) {
if (a == 5) {
char *x = __builtin_alloca(a);
x[4] = 4; // no-warning
x[5] = 5; // expected-warning{{Out of bound access}}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alloca is tested by allocaRegion in out-of-bounds-diagnostics.c; symbolic extent is tested by other cases.

Comment on lines -80 to -86
int symbolic_index(int a) {
int x[2] = {1, 2};
if (a == 2) {
return x[a]; // expected-warning{{Out of bound access}}
}
return 0;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This "weak" symbolic index (which is assumed to be exactly equal to a concrete value) is AFAIK not explicitly tested; but it is not a relevant case because the analyzer engine represents it with the same nonloc::ConcreteInt which would represent an integer literal 2.

Comment on lines -88 to -94
int symbolic_index2(int a) {
int x[2] = {1, 2};
if (a < 0) {
return x[a]; // expected-warning{{Out of bound access}}
}
return 0;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Symbolic indices are tested in very many testcases, including e.g. symbolicIndex in out-of-bounds-diagnostics.c.

Comment on lines -96 to -130
int overflow_binary_search(double in) {
int eee = 16;
if (in < 1e-8 || in > 1e23) {
return 0;
} else {
static const double ins[] = {1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1,
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
if (in < ins[eee]) {
eee -= 8;
} else {
eee += 8;
}
if (in < ins[eee]) {
eee -= 4;
} else {
eee += 4;
}
if (in < ins[eee]) {
eee -= 2;
} else {
eee += 2;
}
if (in < ins[eee]) {
eee -= 1;
} else {
eee += 1;
}
if (in < ins[eee]) { // expected-warning {{Out of bound access}}
eee -= 1;
}
}
return eee;
}
Copy link
Contributor Author

@NagyDonat NagyDonat Feb 24, 2025

Choose a reason for hiding this comment

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

This test case is long and convoluted, but does not provide anything interesting from the point of view of the array bound checker.

The bug report is a very straightforward one: the value of eee can be 16 + 8 + 4 + 2 + 1 = 31 (on the branch that always takes else) while the length of the array is only 31 (8 elements with negative exponents + 1 + 22 with positive exponents). This is difficult to spot and understand for a human reader, but does not require unusual effort from the analyzer -- which is exactly the opposite of a good testcase.

By the way this case was added by commit 2d10fdd in 2012 without any explanation or correlated code change.

Copy link
Contributor

@gamesh411 gamesh411 left a comment

Choose a reason for hiding this comment

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

This is a nice cleanup change. I have checked the test cases and the reasoning behind the removals.
It is also welcome that these reasons are documented, at least in the PR.
@NagyDonat What do you think about making these reasoning snippets available in the commit message?
I am not sure that it is enough for them to only exist in the PR comments, but if you say that it would be unnecessary verbose to have the gist of them in the commit message, that is fine as well.

@NagyDonat
Copy link
Contributor Author

I think the "why were these testcases redundant?" information is too verbose for a commit message; moreover it only conveys information about code that was removed from the repository, so it won't be relevant for readers in the future who are presumably only interested in code that stays in the repository.

@NagyDonat
Copy link
Contributor Author

The failing tests are unrelated to the commit, apparently a script called generate_test_report.py exits with failure.

@NagyDonat NagyDonat merged commit 71389e5 into llvm:main Feb 28, 2025
11 of 14 checks passed
cheezeburglar pushed a commit to cheezeburglar/llvm-project that referenced this pull request Feb 28, 2025
…128508)

Before commit 6e17ed9 the test files `outofbound.c` and
`outofbound-notwork.c` tested the behavior of the old alpha checker
`alpha.security.ArrayBound` (V1); then that commit converted them into
tests for the checker `security.ArrayBound` which was previously called
`alpha.security.ArrayBoundV2`.

This commit removes these test files and migrates their useful content
to `out-of-bounds.c`. The file `outofbound.c` contained lots of
testcases that covered features which are also covered in
`out-of-bounds.c` or `out-of-bounds-diagnostics.c`; those redundant
cases are discarded during this migration process.

This is part of a commit series that reorganizes the tests of
`security.ArrayBound` to a system that's easier to understand and
maintain.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants