llvm-project/clang/test/Analysis/explain-svals.cpp
Balazs Benics 9b2ec87f5b
[analyzer] Avoid creating LazyCompoundVal when possible (#116840)
In #115916 I allowed copying empty structs.
Later in #115917 I changed how objects are copied, and basically when we
would want to copy a struct (an LCV) of a single symbol (likely coming
from an opaque fncall or invalidation), just directly bind that symbol
instead of creating an LCV referring to the symbol. This was an
optimization to skip a layer of indirection.

Now, it turns out I should have apply the same logic in #115916. I
should not have just blindly created an LCV by calling
`createLazyBinding()`, but rather check if I can apply the shortcut
described in #115917 and only create the LCV if the shortcut doesn't
apply.

In this patch I check if there is a single default binding that the copy
would refer to and if so, just return that symbol instead of creating an
LCV.

There shouldn't be any observable changes besides that we should have
fewer LCVs. This change may surface bugs in checkers that were
associating some metadata with entities in a wrong way. Notably,
STLAlgorithmModeling and DebugIteratorModeling checkers would likely
stop working after this change.
I didn't investigate them deeply because they were broken even prior to
this patch. Let me know if I should migrate these checkers to be just as
bugged as they were prior to this patch - thus make the tests pass.
2024-11-29 09:19:33 +01:00

132 lines
5.9 KiB
C++

// RUN: %clang_analyze_cc1 -triple i386-apple-darwin10 -verify %s \
// RUN: -analyzer-checker=debug.ExprInspection \
// RUN: -analyzer-checker=unix.cstring \
// RUN: -analyzer-checker=unix.Malloc \
// RUN: -analyzer-config display-checker-name=false
typedef unsigned long size_t;
struct S {
struct S3 {
int y[10];
};
struct S2 : S3 {
int *x;
} s2[10];
int z;
};
void clang_analyzer_explain(int);
void clang_analyzer_explain(void *);
void clang_analyzer_explain(const int *);
void clang_analyzer_explain(S);
size_t clang_analyzer_getExtent(void *);
size_t strlen(const char *);
int conjure();
S conjure_S();
int glob;
static int stat_glob;
void *glob_ptr;
// Test strings are regex'ed because we need to match exact string
// rather than a substring.
void test_1(int param, void *ptr) {
clang_analyzer_explain(&glob); // expected-warning-re{{{{^pointer to global variable 'glob'$}}}}
clang_analyzer_explain(param); // expected-warning-re{{{{^argument 'param'$}}}}
clang_analyzer_explain(ptr); // expected-warning-re{{{{^argument 'ptr'$}}}}
if (param == 42)
clang_analyzer_explain(param); // expected-warning-re{{{{^signed 32-bit integer '42'$}}}}
}
void test_2(char *ptr, int ext) {
clang_analyzer_explain((void *) "asdf"); // expected-warning-re{{{{^pointer to element of type 'char' with index 0 of string literal "asdf"$}}}}
clang_analyzer_explain(strlen(ptr)); // expected-warning-re{{{{^metadata of type 'unsigned long' tied to pointee of argument 'ptr'$}}}}
clang_analyzer_explain(conjure()); // expected-warning-re{{{{^symbol of type 'int' conjured at statement 'conjure\(\)'$}}}}
clang_analyzer_explain(glob); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at statement 'conjure\(\)'\) for global variable 'glob'$}}}}
clang_analyzer_explain(glob_ptr); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at statement 'conjure\(\)'\) for global variable 'glob_ptr'$}}}}
clang_analyzer_explain(clang_analyzer_getExtent(ptr)); // expected-warning-re{{{{^extent of pointee of argument 'ptr'$}}}}
int *x = new int[ext];
clang_analyzer_explain(x); // expected-warning-re{{{{^pointer to element of type 'int' with index 0 of heap segment that starts at symbol of type 'int \*' conjured at statement 'new int \[ext\]'$}}}}
// Sic! What gets computed is the extent of the element-region.
clang_analyzer_explain(clang_analyzer_getExtent(x)); // expected-warning-re{{{{^\(argument 'ext'\) \* 4$}}}}
delete[] x;
}
void test_3(S s) {
clang_analyzer_explain(&s); // expected-warning-re{{{{^pointer to parameter 's'$}}}}
clang_analyzer_explain(s.z); // expected-warning-re{{{{^initial value of field 'z' of parameter 's'$}}}}
clang_analyzer_explain(&s.s2[5].y[3]); // expected-warning-re{{{{^pointer to element of type 'int' with index 3 of field 'y' of base object 'S::S3' inside element of type 'struct S::S2' with index 5 of field 's2' of parameter 's'$}}}}
if (!s.s2[7].x) {
clang_analyzer_explain(s.s2[7].x); // expected-warning-re{{{{^concrete memory address '0'$}}}}
// FIXME: we need to be explaining '1' rather than '0' here; not explainer bug.
clang_analyzer_explain(s.s2[7].x + 1); // expected-warning-re{{{{^concrete memory address '0'$}}}}
}
}
void test_4(int x, int y) {
int z;
static int stat;
clang_analyzer_explain(-x); // expected-warning-re{{{{^\- \(argument 'x'\)$}}}}
clang_analyzer_explain(x + 1); // expected-warning-re{{{{^\(argument 'x'\) \+ 1$}}}}
clang_analyzer_explain(1 + y); // expected-warning-re{{{{^\(argument 'y'\) \+ 1$}}}}
clang_analyzer_explain(x + y); // expected-warning-re{{{{^\(argument 'x'\) \+ \(argument 'y'\)$}}}}
clang_analyzer_explain(z); // expected-warning-re{{{{^undefined value$}}}}
clang_analyzer_explain(&z); // expected-warning-re{{{{^pointer to local variable 'z'$}}}}
clang_analyzer_explain(stat); // expected-warning-re{{{{^signed 32-bit integer '0'$}}}}
clang_analyzer_explain(&stat); // expected-warning-re{{{{^pointer to static local variable 'stat'$}}}}
clang_analyzer_explain(stat_glob); // expected-warning-re{{{{^initial value of global variable 'stat_glob'$}}}}
clang_analyzer_explain(&stat_glob); // expected-warning-re{{{{^pointer to global variable 'stat_glob'$}}}}
clang_analyzer_explain((int[]){1, 2, 3}); // expected-warning-re{{{{^pointer to element of type 'int' with index 0 of temporary object constructed at statement '\(int\[3\]\)\{1, 2, 3\}'$}}}}
}
namespace {
class C {
int x[10];
public:
void test_5(int i) {
clang_analyzer_explain(this); // expected-warning-re{{{{^pointer to 'this' object$}}}}
clang_analyzer_explain(&x[i]); // expected-warning-re{{{{^pointer to element of type 'int' with index 'argument 'i'' of field 'x' of 'this' object$}}}}
clang_analyzer_explain(__builtin_alloca(i)); // expected-warning-re{{{{^pointer to region allocated by '__builtin_alloca\(i\)'$}}}}
}
};
} // end of anonymous namespace
void test_6() {
clang_analyzer_explain(conjure_S()); // expected-warning-re{{{{^symbol of type 'int' conjured at statement 'conjure_S\(\)'$}}}}
clang_analyzer_explain(conjure_S().z); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at statement 'conjure_S\(\)'\) for field 'z' of temporary object constructed at statement 'conjure_S\(\)'$}}}}
}
class C_top_level {
public:
C_top_level(int param) {
clang_analyzer_explain(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
}
};
class C_non_top_level {
public:
C_non_top_level(int param) {
clang_analyzer_explain(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
}
};
void test_7(int n) {
C_non_top_level c(n);
auto lambda_top_level = [n](int param) {
clang_analyzer_explain(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
};
auto lambda_non_top_level = [n](int param) {
clang_analyzer_explain(&param); // expected-warning-re{{{{^pointer to parameter 'param'$}}}}
};
lambda_non_top_level(n);
}