|
1 | | -// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -verify %s |
| 1 | +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -verify=expected,eagerlyassume %s |
| 2 | +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -analyzer-config eagerly-assume=false -verify %s |
| 3 | + |
| 4 | +// Note that eagerly-assume=false is tested separately because the |
| 5 | +// WeakLoopAssumption suppression heuristic uses different code paths to |
| 6 | +// achieve the same result with and without eagerly-assume. |
2 | 7 |
|
3 | 8 | void clang_analyzer_eval(int); |
4 | 9 |
|
@@ -194,3 +199,146 @@ char test_comparison_with_extent_symbol(struct incomplete *p) { |
194 | 199 | return ((char *)p)[-1]; // no-warning |
195 | 200 | } |
196 | 201 |
|
| 202 | +// WeakLoopAssumption suppression |
| 203 | +/////////////////////////////////////////////////////////////////////// |
| 204 | + |
| 205 | +int GlobalArray[100]; |
| 206 | +int loop_suppress_after_zero_iterations(unsigned len) { |
| 207 | + for (unsigned i = 0; i < len; i++) |
| 208 | + if (GlobalArray[i] > 0) |
| 209 | + return GlobalArray[i]; |
| 210 | + // Previously this would have produced an overflow warning because splitting |
| 211 | + // the state on the loop condition introduced an execution path where the |
| 212 | + // analyzer thinks that len == 0. |
| 213 | + // There are many situations where the programmer knows that an argument is |
| 214 | + // positive, but this is not indicated in the source code, so we must avoid |
| 215 | + // reporting errors (especially out of bounds errors) on these branches, |
| 216 | + // because otherwise we'd get prohibitively many false positives. |
| 217 | + return GlobalArray[len - 1]; // no-warning |
| 218 | +} |
| 219 | + |
| 220 | +void loop_report_in_second_iteration(int len) { |
| 221 | + int buf[1] = {0}; |
| 222 | + for (int i = 0; i < len; i++) { |
| 223 | + // When a programmer writes a loop, we may assume that they intended at |
| 224 | + // least two iterations. |
| 225 | + buf[i] = 1; // expected-warning{{Out of bound access to memory}} |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +void loop_suppress_in_third_iteration(int len) { |
| 230 | + int buf[2] = {0}; |
| 231 | + for (int i = 0; i < len; i++) { |
| 232 | + // We should suppress array bounds errors on the third and later iterations |
| 233 | + // of loops, because sometimes programmers write a loop in sitiuations |
| 234 | + // where they know that there will be at most two iterations, but the |
| 235 | + // analyzer cannot deduce this property. |
| 236 | + buf[i] = 1; // no-warning |
| 237 | + } |
| 238 | +} |
| 239 | + |
| 240 | +int no_suppression_when_no_assumption_zero_iterations(unsigned len) { |
| 241 | + if (len != 0) { |
| 242 | + // This 'if' introduces a state split between len == 0 and len != 0. |
| 243 | + } |
| 244 | + |
| 245 | + // On the branch where we assumed that len is zero, this loop will be |
| 246 | + // skipped. We (intentionally) don't suppress this execution path becasue |
| 247 | + // here the analyzer doesn't assume anything new when it evaluates the loop |
| 248 | + // condition. |
| 249 | + for (unsigned i = 0; i < len; i++) |
| 250 | + if (GlobalArray[i] > 0) |
| 251 | + return GlobalArray[i]; |
| 252 | + |
| 253 | + return GlobalArray[len - 1]; // expected-warning{{Out of bound access to memory}} |
| 254 | +} |
| 255 | + |
| 256 | +void no_suppression_when_no_assumption_third_iteration(int len) { |
| 257 | + if (len < 20) { |
| 258 | + // This 'if' introduces a state split with len >= 20 on one branch. |
| 259 | + } |
| 260 | + |
| 261 | + int buf[2] = {0}; |
| 262 | + for (int i = 0; i < len; i++) { |
| 263 | + // As in no_suppression_when_no_assumption_zero_iterations, the suppression |
| 264 | + // only activates when the analyzer assumes something new in the loop |
| 265 | + // condition. On the branch where `len >= 20` entering the third iteration |
| 266 | + // doesn't involve a new assumption, so this warning is not suppressed: |
| 267 | + buf[i] = 1; // expected-warning{{Out of bound access to memory}} |
| 268 | + } |
| 269 | +} |
| 270 | + |
| 271 | +void loop_suppress_in_third_iteration_logical_and(int len, int flag) { |
| 272 | + int buf[2] = {0}; |
| 273 | + for (int i = 0; i < len && flag; i++) { |
| 274 | + // FIXME: In this case the checker should suppress the warning the same way |
| 275 | + // as it's suppressed in loop_suppress_in_third_iteration, but the |
| 276 | + // suppression is not activated because the terminator statement associated |
| 277 | + // with the loop is just the expression 'flag', while 'i < len' is a |
| 278 | + // separate terminator statement that's associated with the |
| 279 | + // short-circuiting operator '&&'. |
| 280 | + // I have seen a real-world FP that looks like this, but it is much rarer |
| 281 | + // than the basic setup. |
| 282 | + buf[i] = 1; // expected-warning{{Out of bound access to memory}} |
| 283 | + } |
| 284 | +} |
| 285 | + |
| 286 | +void loop_suppress_in_third_iteration_logical_and_2(int len, int flag) { |
| 287 | + int buf[2] = {0}; |
| 288 | + for (int i = 0; flag && i < len; i++) { |
| 289 | + // If the two operands of '&&' are flipped, the suppression works, because |
| 290 | + // then 'flag' is the terminator statement associated with '&&' (which |
| 291 | + // determines whether the short-circuiting happens or not) and 'i < len' is |
| 292 | + // the terminator statement of the loop itself. |
| 293 | + buf[i] = 1; // no-warning |
| 294 | + } |
| 295 | +} |
| 296 | + |
| 297 | +void loop_suppress_in_third_iteration_cast(int len) { |
| 298 | + int buf[2] = {0}; |
| 299 | + for (int i = 0; (unsigned)(i < len); i++) { |
| 300 | + // The behavior of this suppression is slightly different under |
| 301 | + // eagerly-assume=true (the default) and eagerly-assume=false: |
| 302 | + // * When eager assumptions are disabled, it's enough to look for cases |
| 303 | + // where we get two non-null states from splitting the state over the |
| 304 | + // 'SVal' that represents the full loop condition. |
| 305 | + // * When eager assumptions are enabled, we also accept situations when the |
| 306 | + // loop condition expression triggers an eager state split and therefore |
| 307 | + // we won't see a state split at the "normal" point because it's executed |
| 308 | + // on two already separated execution paths. |
| 309 | + // However, for the sake of simplicity we don't activate the suppression in |
| 310 | + // cases when _a subexpression_ of the loop condition triggers an eager |
| 311 | + // assumption. There are already many differences between analysis with and |
| 312 | + // without eager assumptions, so it would be pointless to write more |
| 313 | + // complicated code to eliminate these rare differences. |
| 314 | + buf[i] = 1; // eagerlyassume-warning{{Out of bound access to memory}} |
| 315 | + } |
| 316 | +} |
| 317 | + |
| 318 | +int coinflip(void); |
| 319 | +int do_while_report_after_one_iteration(void) { |
| 320 | + int i = 0; |
| 321 | + do { |
| 322 | + i++; |
| 323 | + } while (coinflip()); |
| 324 | + // Unlike `loop_suppress_after_zero_iterations`, running just one iteration |
| 325 | + // in a do-while is not a corner case that would produce too many false |
| 326 | + // positives, so don't suppress bounds errors in these situations. |
| 327 | + return GlobalArray[i-2]; // expected-warning{{Out of bound access to memory}} |
| 328 | +} |
| 329 | + |
| 330 | +void do_while_report_in_second_iteration(int len) { |
| 331 | + int buf[1] = {0}; |
| 332 | + int i = 0; |
| 333 | + do { |
| 334 | + buf[i] = 1; // expected-warning{{Out of bound access to memory}} |
| 335 | + } while (i++ < len); |
| 336 | +} |
| 337 | + |
| 338 | +void do_while_suppress_in_third_iteration(int len) { |
| 339 | + int buf[2] = {0}; |
| 340 | + int i = 0; |
| 341 | + do { |
| 342 | + buf[i] = 1; // no-warning |
| 343 | + } while (i++ < len); |
| 344 | +} |
0 commit comments