analyzer: Handle strdup builtins

Consolidate allocator builtin handling and add support for
__builtin_strdup and __builtin_strndup.

gcc/analyzer/ChangeLog:

	* analyzer.cc (is_named_call_p, is_std_named_call_p): Make
	first argument a const_tree.
	* analyzer.h (is_named_call_p, -s_std_named_call_p): Likewise.
	* sm-malloc.cc (known_allocator_p): New function.
	(malloc_state_machine::on_stmt): Use it.

gcc/testsuite/ChangeLog:

	* gcc.dg/analyzer/strdup-1.c (test_4, test_5, test_6): New
	tests.
This commit is contained in:
Siddhesh Poyarekar 2021-07-28 15:43:47 +05:30
parent 84606efb0c
commit 31534ac26e
4 changed files with 60 additions and 16 deletions

View File

@ -240,7 +240,7 @@ is_special_named_call_p (const gcall *call, const char *funcname,
Compare with special_function_p in calls.c. */
bool
is_named_call_p (tree fndecl, const char *funcname)
is_named_call_p (const_tree fndecl, const char *funcname)
{
gcc_assert (fndecl);
gcc_assert (funcname);
@ -292,7 +292,7 @@ is_std_function_p (const_tree fndecl)
/* Like is_named_call_p, but look for std::FUNCNAME. */
bool
is_std_named_call_p (tree fndecl, const char *funcname)
is_std_named_call_p (const_tree fndecl, const char *funcname)
{
gcc_assert (fndecl);
gcc_assert (funcname);
@ -314,7 +314,7 @@ is_std_named_call_p (tree fndecl, const char *funcname)
arguments? */
bool
is_named_call_p (tree fndecl, const char *funcname,
is_named_call_p (const_tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args)
{
gcc_assert (fndecl);
@ -332,7 +332,7 @@ is_named_call_p (tree fndecl, const char *funcname,
/* Like is_named_call_p, but check for std::FUNCNAME. */
bool
is_std_named_call_p (tree fndecl, const char *funcname,
is_std_named_call_p (const_tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args)
{
gcc_assert (fndecl);

View File

@ -220,11 +220,11 @@ enum access_direction
extern bool is_special_named_call_p (const gcall *call, const char *funcname,
unsigned int num_args);
extern bool is_named_call_p (tree fndecl, const char *funcname);
extern bool is_named_call_p (tree fndecl, const char *funcname,
extern bool is_named_call_p (const_tree fndecl, const char *funcname);
extern bool is_named_call_p (const_tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args);
extern bool is_std_named_call_p (tree fndecl, const char *funcname);
extern bool is_std_named_call_p (tree fndecl, const char *funcname,
extern bool is_std_named_call_p (const_tree fndecl, const char *funcname);
extern bool is_std_named_call_p (const_tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args);
extern bool is_setjmp_call_p (const gcall *call);
extern bool is_longjmp_call_p (const gcall *call);

View File

@ -1526,6 +1526,38 @@ malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl)
return d;
}
/* Try to identify the function declaration either by name or as a known malloc
builtin. */
static bool
known_allocator_p (const_tree fndecl, const gcall *call)
{
/* Either it is a function we know by name and number of arguments... */
if (is_named_call_p (fndecl, "malloc", call, 1)
|| is_named_call_p (fndecl, "calloc", call, 2)
|| is_std_named_call_p (fndecl, "malloc", call, 1)
|| is_std_named_call_p (fndecl, "calloc", call, 2)
|| is_named_call_p (fndecl, "strdup", call, 1)
|| is_named_call_p (fndecl, "strndup", call, 2))
return true;
/* ... or it is a builtin allocator that allocates objects freed with
__builtin_free. */
if (fndecl_built_in_p (fndecl))
switch (DECL_FUNCTION_CODE (fndecl))
{
case BUILT_IN_MALLOC:
case BUILT_IN_CALLOC:
case BUILT_IN_STRDUP:
case BUILT_IN_STRNDUP:
return true;
default:
break;
}
return false;
}
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
bool
@ -1536,14 +1568,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
if (const gcall *call = dyn_cast <const gcall *> (stmt))
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
{
if (is_named_call_p (callee_fndecl, "malloc", call, 1)
|| is_named_call_p (callee_fndecl, "calloc", call, 2)
|| is_std_named_call_p (callee_fndecl, "malloc", call, 1)
|| is_std_named_call_p (callee_fndecl, "calloc", call, 2)
|| is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1)
|| is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2)
|| is_named_call_p (callee_fndecl, "strdup", call, 1)
|| is_named_call_p (callee_fndecl, "strndup", call, 2))
if (known_allocator_p (callee_fndecl, call))
{
on_allocator_call (sm_ctxt, call, &m_free);
return true;

View File

@ -14,8 +14,27 @@ void test_2 (const char *s)
char *p = strdup (s);
free (p);
}
void test_3 (const char *s)
{
char *p = strdup (s); /* { dg-message "this call could return NULL" } */
requires_nonnull (p); /* { dg-warning "use of possibly-NULL 'p'" } */
}
/* Repeat tests for __builtin_strdup. */
void test_4 (const char *s)
{
char *p = __builtin_strdup (s); /* { dg-message "allocated here" } */
} /* { dg-warning "leak of 'p'" } */
void test_5 (const char *s)
{
char *p = __builtin_strdup (s);
free (p);
}
void test_6 (const char *s)
{
char *p = __builtin_strdup (s); /* { dg-message "this call could return NULL" } */
requires_nonnull (p); /* { dg-warning "use of possibly-NULL 'p'" } */
}