301 changes: 166 additions & 135 deletions clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Large diffs are not rendered by default.

89 changes: 86 additions & 3 deletions clang/test/Analysis/bsd-string.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
typedef __typeof(sizeof(int)) size_t;
size_t strlcpy(char *dst, const char *src, size_t n);
size_t strlcat(char *dst, const char *src, size_t n);
size_t strlen(const char *s);
void clang_analyzer_eval(int);

void f1() {
Expand All @@ -18,9 +19,11 @@ void f1() {

void f2() {
char buf[5];
strlcpy(buf, "abcd", sizeof(buf)); // expected-no-warning
// FIXME: This should not warn. The string is safely truncated.
strlcat(buf, "efgh", sizeof(buf)); // expected-warning{{Size argument is greater than the free space in the destination buffer}}
size_t len;
len = strlcpy(buf, "abcd", sizeof(buf)); // expected-no-warning
clang_analyzer_eval(len == 4); // expected-warning{{TRUE}}
len = strlcat(buf, "efgh", sizeof(buf)); // expected-no-warning
clang_analyzer_eval(len == 8); // expected-warning{{TRUE}}
}

void f3() {
Expand Down Expand Up @@ -48,3 +51,83 @@ int f7() {
char buf[8];
return strlcpy(buf, "1234567", 0); // no-crash
}

void f8(){
char buf[5];
size_t len;

// basic strlcpy
len = strlcpy(buf,"123", sizeof(buf));
clang_analyzer_eval(len==3);// expected-warning{{TRUE}}
len = strlen(buf);
clang_analyzer_eval(len==3);// expected-warning{{TRUE}}

// testing bounded strlcat
len = strlcat(buf,"456", sizeof(buf));
clang_analyzer_eval(len==6);// expected-warning{{TRUE}}
len = strlen(buf);
clang_analyzer_eval(len==4);// expected-warning{{TRUE}}

// testing strlcat with size==0
len = strlcat(buf,"789", 0);
clang_analyzer_eval(len==7);// expected-warning{{TRUE}}
len = strlen(buf);
clang_analyzer_eval(len==4);// expected-warning{{TRUE}}

// testing strlcpy with size==0
len = strlcpy(buf,"123",0);
clang_analyzer_eval(len==3);// expected-warning{{TRUE}}
len = strlen(buf);
clang_analyzer_eval(len==4);// expected-warning{{TRUE}}

}

void f9(int unknown_size, char* unknown_src, char* unknown_dst){
char buf[8];
size_t len;

len = strlcpy(buf,"abba",sizeof(buf));

clang_analyzer_eval(len==4);// expected-warning{{TRUE}}
clang_analyzer_eval(strlen(buf)==4);// expected-warning{{TRUE}}

//size is unknown
len = strlcat(buf,"cd", unknown_size);
clang_analyzer_eval(len==6);// expected-warning{{TRUE}}
clang_analyzer_eval(strlen(buf)>=4);// expected-warning{{TRUE}}

//dst is unknown
len = strlcpy(unknown_dst,"abbc",unknown_size);
clang_analyzer_eval(len==4);// expected-warning{{TRUE}}
clang_analyzer_eval(strlen(unknown_dst));// expected-warning{{UNKNOWN}}

//src is unknown
len = strlcpy(buf,unknown_src, sizeof(buf));
clang_analyzer_eval(len);// expected-warning{{UNKNOWN}}
clang_analyzer_eval(strlen(buf));// expected-warning{{UNKNOWN}}

//src, dst is unknown
len = strlcpy(unknown_dst, unknown_src, unknown_size);
clang_analyzer_eval(len);// expected-warning{{UNKNOWN}}
clang_analyzer_eval(strlen(unknown_dst));// expected-warning{{UNKNOWN}}

//size is unknown
len = strlcat(buf+2,unknown_src+1, sizeof(buf));// expected-warning{{Size argument is greater than the length of the destination buffer}};
}

void f10(){
char buf[8];
size_t len;

len = strlcpy(buf,"abba",sizeof(buf));
clang_analyzer_eval(len==4);// expected-warning{{TRUE}}
strlcat(buf, "efghi",9);// expected-warning{{Size argument is greater than the length of the destination buffer}}
}

void f11() {
//test for Bug 41729
char a[256], b[256];
strlcpy(a, "world", sizeof(a));
strlcpy(b, "hello ", sizeof(b));
strlcat(b, a, sizeof(b)); // no-warning
}
116 changes: 116 additions & 0 deletions clang/test/Analysis/handle_constructors_for_default_arguments.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// RUN: %clang_cc1 -fsyntax-only -analyze \
// RUN: -analyzer-checker=core,debug.ExprInspection %s -verify

// These test cases demonstrate lack of Static Analyzer features.
// The FIXME: tags indicate where we expect different output.

// Handle constructors for default arguments.
// Default arguments in C++ are recomputed at every call,
// and are therefore local, and not static, variables.
void clang_analyzer_eval(bool);
void clang_analyzer_warnIfReached();

struct init_with_list {
int a;
init_with_list() : a(1) {}
};

struct init_in_body {
int a;
init_in_body() { a = 1; }
};

struct init_default_member {
int a = 1;
};

struct basic_struct {
int a;
};

// Top-level analyzed functions.
void top_f(init_with_list l = init_with_list()) {
// We expect that the analyzer doesn't assume anything about the parameter.
clang_analyzer_eval(l.a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void top_g(init_in_body l = init_in_body()) {
// We expect that the analyzer doesn't assume anything about the parameter.
clang_analyzer_eval(l.a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void top_h(init_default_member l = init_default_member()) {
// We expect that the analyzer doesn't assume anything about the parameter.
clang_analyzer_eval(l.a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}

// Not-top-level analyzed functions.
int called_f(init_with_list l = init_with_list()) {
// We expect that the analyzer assumes the default value
// when called from test2().
return l.a;
}

int called_g(init_in_body l = init_in_body()) {
// We expect that the analyzer assumes the default value
// when called from test3().
return l.a;
}

int called_h(init_default_member l = init_default_member()) {
// We expect that the analyzer assumes the default value
// when called from test4().
return l.a;
}

int called_i(const init_with_list &l = init_with_list()){
// We expect that the analyzer assumes the default value
// when called from test5().
return l.a;
}

int called_j(init_with_list &&l = init_with_list()){
// We expect that the analyzer assumes the default value
// when called from test6().
return l.a;
}

int plain_parameter_passing(basic_struct l) {
return l.a;
}

void test1() {
basic_struct b;
b.a = 1;
clang_analyzer_eval(plain_parameter_passing(b) == 1); //expected-warning {{TRUE}}
}

void test2() {
// We expect that the analyzer assumes the default value.
// FIXME: Should be TRUE.
clang_analyzer_eval(called_f() == 1); //expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void test3() {
// We expect that the analyzer assumes the default value.
// FIXME: Should be TRUE.
clang_analyzer_eval(called_g() == 1); //expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void test4() {
// We expect that the analyzer assumes the default value.
// FIXME: Should be TRUE.
clang_analyzer_eval(called_h() == 1); //expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void test5() {
//We expect that the analyzer assumes the default value.
// FIXME: Should be TRUE.
clang_analyzer_eval(called_i() == 1); //expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void test6() {
// We expect that the analyzer assumes the default value.
// FIXME: Should be TRUE.
clang_analyzer_eval(called_j() == 1); //expected-warning {{TRUE}} expected-warning {{FALSE}}
}
86 changes: 86 additions & 0 deletions clang/test/Analysis/handle_constructors_with_new_array.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// RUN: %clang_cc1 -fsyntax-only -analyze \
// RUN: -analyzer-checker=core,debug.ExprInspection %s -verify

// These test cases demonstrate lack of Static Analyzer features.
// The FIXME: tags indicate where we expect different output.

// Handle constructors within new[].

// When an array of objects is allocated using the operator new[],
// constructors for all elements of the array are called.
// We should model (potentially some of) such evaluations,
// and the same applies for destructors called from operator delete[].

void clang_analyzer_eval(bool);

struct init_with_list {
int a;
init_with_list() : a(1) {}
};

struct init_in_body {
int a;
init_in_body() { a = 1; }
};

struct init_default_member {
int a = 1;
};

void test_automatic() {

init_with_list a1;
init_in_body a2;
init_default_member a3;

clang_analyzer_eval(a1.a == 1); // expected-warning {{TRUE}}
clang_analyzer_eval(a2.a == 1); // expected-warning {{TRUE}}
clang_analyzer_eval(a3.a == 1); // expected-warning {{TRUE}}
}

void test_dynamic() {

auto *a1 = new init_with_list;
auto *a2 = new init_in_body;
auto *a3 = new init_default_member;

clang_analyzer_eval(a1->a == 1); // expected-warning {{TRUE}}
clang_analyzer_eval(a2->a == 1); // expected-warning {{TRUE}}
clang_analyzer_eval(a3->a == 1); // expected-warning {{TRUE}}

delete a1;
delete a2;
delete a3;
}

void test_automatic_aggregate() {

init_with_list a1[1];
init_in_body a2[1];
init_default_member a3[1];

// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
}

void test_dynamic_aggregate() {

auto *a1 = new init_with_list[1];
auto *a2 = new init_in_body[1];
auto *a3 = new init_default_member[1];

// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a1[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a2[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}
// FIXME: Should be TRUE, not FALSE.
clang_analyzer_eval(a3[0].a == 1); // expected-warning {{TRUE}} expected-warning {{FALSE}}

delete[] a1;
delete[] a2;
delete[] a3;
}
43 changes: 40 additions & 3 deletions clang/www/analyzer/open_projects.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h1>Open Projects</h1>

<li>Improve C++ support
<ul>
<li>Handle aggregate construction.
<li>Handle construction as part of aggregate initialization.
<p><a href="https://en.cppreference.com/w/cpp/language/aggregate_initialization">Aggregates</a>
are objects that can be brace-initialized without calling a
constructor (that is, <code><a href="https://clang.llvm.org/doxygen/classclang_1_1CXXConstructExpr.html">
Expand All @@ -89,12 +89,31 @@ <h1>Open Projects</h1>
<p><i>(Difficulty: Medium) </i></p></p>
</li>

<li>Handle constructors within <code>new[]</code>
<p>When an array of objects is allocated using the <code>operator new[]</code>,
<li>Handle array constructors.
<p>When an array of objects is allocated (say, using the
<code>operator new[]</code> or defining a stack array),
constructors for all elements of the array are called.
We should model (potentially some of) such evaluations,
and the same applies for destructors called from
<code>operator delete[]</code>.
See tests cases in <a href="https://github.com/llvm/llvm-project/tree/master/clang/test/Analysis/handle_constructors_with_new_array.cpp">handle_constructors_with_new_array.cpp</a>.
</p>
<p>
Constructing an array requires invoking multiple (potentially unknown)
amount of constructors with the same construct-expression.
Apart from the technical difficulties of juggling program points around
correctly to avoid accidentally merging paths together, we'll have to
be a judge on when to exit the loop and how to widen it.
Given that the constructor is going to be a default constructor,
a nice 95% solution might be to execute exactly one constructor and
then default-bind the resulting LazyCompoundVal to the whole array;
it'll work whenever the default constructor doesn't touch global state
but only initializes the object to various default values.
But if, say, we're making an array of strings,
depending on the implementation you might have to allocate a new buffer
for each string, and in this case default-binding won't cut it.
We might want to come up with an auxiliary analysis in order to perform
widening of these simple loops more precisely.
</p>
</li>

Expand All @@ -116,6 +135,24 @@ <h1>Open Projects</h1>
<li>Handle constructors for default arguments
<p>Default arguments in C++ are recomputed at every call,
and are therefore local, and not static, variables.
See tests cases in <a href="https://github.com/llvm/llvm-project/tree/master/clang/test/Analysis/handle_constructors_for_default_arguments.cpp">handle_constructors_for_default_arguments.cpp</a>.
</p>
<p>
Default arguments are annoying because the initializer expression is
evaluated at the call site but doesn't syntactically belong to the
caller's AST; instead it belongs to the ParmVarDecl for the default
parameter. This can lead to situations when the same expression has to
carry different values simultaneously -
when multiple instances of the same function are evaluated as part of the
same full-expression without specifying the default arguments.
Even simply calling the function twice (not necessarily within the
same full-expression) may lead to program points agglutinating because
it's the same expression. There are some nasty test cases already
in temporaries.cpp (struct DefaultParam and so on). I recommend adding a
new LocationContext kind specifically to deal with this problem. It'll
also help you figure out the construction context when you evaluate the
construct-expression (though you might still need to do some additional
CFG work to get construction contexts right).
</p>
</li>

Expand Down