Commit 342e14ff by David Malcolm

analyzer: fix setjmp-detection and support sigsetjmp

This patch removes the hack in is_setjmp_call_p of looking for
"setjmp" and "_setjmp", replacing it with some logic adapted from
special_function_p in calls.c, ignoring up to 2 leading underscores from
the fndecl's name when checking for a function by name.

It also requires that such functions are "extern" and at file scope
for them to be matched.

The patch also generalizes the setjmp/longjmp handling in the analyzer
to also work with sigsetjmp/siglongjmp.  Doing so requires generalizing
some hardcoded functions in diagnostics (which were hardcoded to avoid
user-facing messages referring to "_setjmp", which is an implementation
detail) - the patch adds a new function, get_user_facing_name for this,
for use on calls that matched is_named_call_p and
is_specical_named_call_p.

gcc/analyzer/ChangeLog:
	* analyzer.cc  (is_named_call_p): Check that fndecl is "extern"
	and at file scope.  Potentially disregard prefix _ or __ in
	fndecl's name.  Bail if the identifier is NULL.
	(is_setjmp_call_p): Expect a gcall rather than plain gimple.
	Remove special-case check for leading prefix, and also check for
	sigsetjmp.
	(is_longjmp_call_p): Also check for siglongjmp.
	(get_user_facing_name): New function.
	* analyzer.h (is_setjmp_call_p): Expect a gcall rather than plain
	gimple.
	(get_user_facing_name): New decl.
	* checker-path.cc (setjmp_event::get_desc): Use
	get_user_facing_name to avoid hardcoding the function name.
	(rewind_event::rewind_event): Add rewind_info param, using it to
	initialize new m_rewind_info field, and strengthen the assertion.
	(rewind_from_longjmp_event::get_desc): Use get_user_facing_name to
	avoid hardcoding the function name.
	(rewind_to_setjmp_event::get_desc): Likewise.
	* checker-path.h (setjmp_event::setjmp_event): Add setjmp_call
	param and use it to initialize...
	(setjmp_event::m_setjmp_call): New field.
	(rewind_event::rewind_event): Add rewind_info param.
	(rewind_event::m_rewind_info): New protected field.
	(rewind_from_longjmp_event::rewind_from_longjmp_event): Add
	rewind_info param.
	(class rewind_to_setjmp_event): Move rewind_info field to parent
	class.
	* diagnostic-manager.cc (diagnostic_manager::add_events_for_eedge):
	Update setjmp-handling for is_setjmp_call_p requiring a gcall;
	pass the call to the new setjmp_event.
	* engine.cc (exploded_node::on_stmt): Update for is_setjmp_call_p
	requiring a gcall.
	(stale_jmp_buf::emit): Use get_user_facing_name to avoid
	hardcoding the function names.
	(exploded_node::on_longjmp): Pass the longjmp_call when
	constructing rewind_info.
	(rewind_info_t::add_events_to_path): Pass the rewind_info_t to the
	rewind_from_longjmp_event's ctor.
	* exploded-graph.h (rewind_info_t::rewind_info_t): Add
	longjmp_call param.
	(rewind_info_t::get_longjmp_call): New.
	(rewind_info_t::m_longjmp_call): New.
	* region-model.cc (region_model::on_setjmp): Update comment to
	indicate this is also for sigsetjmp.
	* region-model.h (struct setjmp_record): Likewise.
	(class setjmp_svalue): Likewise.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/sigsetjmp-5.c: New test.
	* gcc.dg/analyzer/sigsetjmp-6.c: New test.
parent 317346b2
2020-01-27 David Malcolm <dmalcolm@redhat.com>
* analyzer.cc (is_named_call_p): Check that fndecl is "extern"
and at file scope. Potentially disregard prefix _ or __ in
fndecl's name. Bail if the identifier is NULL.
(is_setjmp_call_p): Expect a gcall rather than plain gimple.
Remove special-case check for leading prefix, and also check for
sigsetjmp.
(is_longjmp_call_p): Also check for siglongjmp.
(get_user_facing_name): New function.
* analyzer.h (is_setjmp_call_p): Expect a gcall rather than plain
gimple.
(get_user_facing_name): New decl.
* checker-path.cc (setjmp_event::get_desc): Use
get_user_facing_name to avoid hardcoding the function name.
(rewind_event::rewind_event): Add rewind_info param, using it to
initialize new m_rewind_info field, and strengthen the assertion.
(rewind_from_longjmp_event::get_desc): Use get_user_facing_name to
avoid hardcoding the function name.
(rewind_to_setjmp_event::get_desc): Likewise.
* checker-path.h (setjmp_event::setjmp_event): Add setjmp_call
param and use it to initialize...
(setjmp_event::m_setjmp_call): New field.
(rewind_event::rewind_event): Add rewind_info param.
(rewind_event::m_rewind_info): New protected field.
(rewind_from_longjmp_event::rewind_from_longjmp_event): Add
rewind_info param.
(class rewind_to_setjmp_event): Move rewind_info field to parent
class.
* diagnostic-manager.cc (diagnostic_manager::add_events_for_eedge):
Update setjmp-handling for is_setjmp_call_p requiring a gcall;
pass the call to the new setjmp_event.
* engine.cc (exploded_node::on_stmt): Update for is_setjmp_call_p
requiring a gcall.
(stale_jmp_buf::emit): Use get_user_facing_name to avoid
hardcoding the function names.
(exploded_node::on_longjmp): Pass the longjmp_call when
constructing rewind_info.
(rewind_info_t::add_events_to_path): Pass the rewind_info_t to the
rewind_from_longjmp_event's ctor.
* exploded-graph.h (rewind_info_t::rewind_info_t): Add
longjmp_call param.
(rewind_info_t::get_longjmp_call): New.
(rewind_info_t::m_longjmp_call): New.
* region-model.cc (region_model::on_setjmp): Update comment to
indicate this is also for sigsetjmp.
* region-model.h (struct setjmp_record): Likewise.
(class setjmp_svalue): Likewise.
2020-01-27 David Malcolm <dmalcolm@redhat.com>
PR analyzer/93276
* analyzer.h (PUSH_IGNORE_WFORMAT, POP_IGNORE_WFORMAT): Guard these
macros with GCC_VERSION >= 4006, making them no-op otherwise.
......
......@@ -54,7 +54,10 @@ is_special_named_call_p (const gcall *call, const char *funcname,
return is_named_call_p (fndecl, funcname, call, num_args);
}
/* Helper function for checkers. Does FNDECL have the given FUNCNAME? */
/* Helper function for checkers. Is FNDECL an extern fndecl at file scope
that has the given FUNCNAME?
Compare with special_function_p in calls.c. */
bool
is_named_call_p (tree fndecl, const char *funcname)
......@@ -62,11 +65,38 @@ is_named_call_p (tree fndecl, const char *funcname)
gcc_assert (fndecl);
gcc_assert (funcname);
return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
/* Exclude functions not at the file scope, or not `extern',
since they are not the magic functions we would otherwise
think they are. */
if (!((DECL_CONTEXT (fndecl) == NULL_TREE
|| TREE_CODE (DECL_CONTEXT (fndecl)) == TRANSLATION_UNIT_DECL)
&& TREE_PUBLIC (fndecl)))
return false;
tree identifier = DECL_NAME (fndecl);
if (identifier == NULL)
return false;
const char *name = IDENTIFIER_POINTER (identifier);
const char *tname = name;
/* Potentially disregard prefix _ or __ in FNDECL's name, but not if
FUNCNAME itself has leading underscores (e.g. when looking for
"__analyzer_eval"). */
if (funcname[0] != '_' && name[0] == '_')
{
if (name[1] == '_')
tname += 2;
else
tname += 1;
}
return 0 == strcmp (tname, funcname);
}
/* Helper function for checkers. Does FNDECL have the given FUNCNAME, and
does CALL have the given number of arguments? */
/* Helper function for checkers. Is FNDECL an extern fndecl at file scope
that has the given FUNCNAME, and does CALL have the given number of
arguments? */
bool
is_named_call_p (tree fndecl, const char *funcname,
......@@ -84,32 +114,57 @@ is_named_call_p (tree fndecl, const char *funcname,
return true;
}
/* Return true if stmt is a setjmp call. */
/* Return true if stmt is a setjmp or sigsetjmp call. */
bool
is_setjmp_call_p (const gimple *stmt)
is_setjmp_call_p (const gcall *call)
{
/* TODO: is there a less hacky way to check for "setjmp"? */
if (const gcall *call = dyn_cast <const gcall *> (stmt))
if (is_special_named_call_p (call, "setjmp", 1)
|| is_special_named_call_p (call, "_setjmp", 1))
return true;
if (is_special_named_call_p (call, "setjmp", 1)
|| is_special_named_call_p (call, "sigsetjmp", 2))
return true;
return false;
}
/* Return true if stmt is a longjmp call. */
/* Return true if stmt is a longjmp or siglongjmp call. */
bool
is_longjmp_call_p (const gcall *call)
{
/* TODO: is there a less hacky way to check for "longjmp"? */
if (is_special_named_call_p (call, "longjmp", 2))
if (is_special_named_call_p (call, "longjmp", 2)
|| is_special_named_call_p (call, "siglongjmp", 2))
return true;
return false;
}
/* For a CALL that matched is_special_named_call_p or is_named_call_p for
some name, return a name for the called function suitable for use in
diagnostics (stripping the leading underscores). */
const char *
get_user_facing_name (const gcall *call)
{
tree fndecl = gimple_call_fndecl (call);
gcc_assert (fndecl);
tree identifier = DECL_NAME (fndecl);
gcc_assert (identifier);
const char *name = IDENTIFIER_POINTER (identifier);
/* Strip prefix _ or __ in FNDECL's name. */
if (name[0] == '_')
{
if (name[1] == '_')
return name + 2;
else
return name + 1;
}
return name;
}
/* Generate a label_text instance by formatting FMT, using a
temporary clone of the global_dc's printer (thus using its
formatting callbacks).
......
......@@ -78,9 +78,11 @@ extern bool is_special_named_call_p (const gcall *call, const char *funcname,
extern bool is_named_call_p (tree fndecl, const char *funcname);
extern bool is_named_call_p (tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args);
extern bool is_setjmp_call_p (const gimple *stmt);
extern bool is_setjmp_call_p (const gcall *call);
extern bool is_longjmp_call_p (const gcall *call);
extern const char *get_user_facing_name (const gcall *call);
extern void register_analyzer_pass ();
extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
......
......@@ -709,7 +709,7 @@ setjmp_event::get_desc (bool can_colorize) const
{
return make_label_text (can_colorize,
"%qs called here",
"setjmp");
get_user_facing_name (m_setjmp_call));
}
/* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event.
......@@ -748,11 +748,13 @@ rewind_event::get_setjmp_caller () const
rewind_event::rewind_event (const exploded_edge *eedge,
enum event_kind kind,
location_t loc, tree fndecl, int depth)
location_t loc, tree fndecl, int depth,
const rewind_info_t *rewind_info)
: checker_event (kind, loc, fndecl, depth),
m_rewind_info (rewind_info),
m_eedge (eedge)
{
gcc_assert (m_eedge->m_custom_info); // a rewind_info_t
gcc_assert (m_eedge->m_custom_info == m_rewind_info);
}
/* class rewind_from_longjmp_event : public rewind_event. */
......@@ -763,7 +765,8 @@ rewind_event::rewind_event (const exploded_edge *eedge,
label_text
rewind_from_longjmp_event::get_desc (bool can_colorize) const
{
const char *src_name = "longjmp";
const char *src_name
= get_user_facing_name (m_rewind_info->get_longjmp_call ());
if (get_longjmp_caller () == get_setjmp_caller ())
/* Special-case: purely intraprocedural rewind. */
......@@ -786,7 +789,8 @@ rewind_from_longjmp_event::get_desc (bool can_colorize) const
label_text
rewind_to_setjmp_event::get_desc (bool can_colorize) const
{
const char *dst_name = "setjmp";
const char *dst_name
= get_user_facing_name (m_rewind_info->get_setjmp_call ());
/* If we can, identify the ID of the setjmp_event. */
if (m_original_setjmp_event_id.known_p ())
......
......@@ -329,15 +329,15 @@ public:
bool is_return_p () const FINAL OVERRIDE;
};
/* A concrete event subclass for a setjmp call. */
/* A concrete event subclass for a setjmp or sigsetjmp call. */
class setjmp_event : public checker_event
{
public:
setjmp_event (location_t loc, const exploded_node *enode,
tree fndecl, int depth)
tree fndecl, int depth, const gcall *setjmp_call)
: checker_event (EK_SETJMP, loc, fndecl, depth),
m_enode (enode)
m_enode (enode), m_setjmp_call (setjmp_call)
{
}
......@@ -349,9 +349,12 @@ public:
private:
const exploded_node *m_enode;
const gcall *m_setjmp_call;
};
/* An abstract event subclass for rewinding from a longjmp to a setjmp.
/* An abstract event subclass for rewinding from a longjmp to a setjmp
(or siglongjmp to sigsetjmp).
Base class for two from/to subclasses, showing the two halves of the
rewind. */
......@@ -365,21 +368,25 @@ public:
protected:
rewind_event (const exploded_edge *eedge,
enum event_kind kind,
location_t loc, tree fndecl, int depth);
location_t loc, tree fndecl, int depth,
const rewind_info_t *rewind_info);
const rewind_info_t *m_rewind_info;
private:
const exploded_edge *m_eedge;
};
/* A concrete event subclass for rewinding from a longjmp to a setjmp,
showing the longjmp. */
showing the longjmp (or siglongjmp). */
class rewind_from_longjmp_event : public rewind_event
{
public:
rewind_from_longjmp_event (const exploded_edge *eedge,
location_t loc, tree fndecl, int depth)
: rewind_event (eedge, EK_REWIND_FROM_LONGJMP, loc, fndecl, depth)
location_t loc, tree fndecl, int depth,
const rewind_info_t *rewind_info)
: rewind_event (eedge, EK_REWIND_FROM_LONGJMP, loc, fndecl, depth,
rewind_info)
{
}
......@@ -387,7 +394,7 @@ public:
};
/* A concrete event subclass for rewinding from a longjmp to a setjmp,
showing the setjmp. */
showing the setjmp (or sigsetjmp). */
class rewind_to_setjmp_event : public rewind_event
{
......@@ -395,8 +402,8 @@ public:
rewind_to_setjmp_event (const exploded_edge *eedge,
location_t loc, tree fndecl, int depth,
const rewind_info_t *rewind_info)
: rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth),
m_rewind_info (rewind_info)
: rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth,
rewind_info)
{
}
......@@ -408,7 +415,6 @@ public:
private:
diagnostic_event_id_t m_original_setjmp_event_id;
const rewind_info_t *m_rewind_info;
};
/* Concrete subclass of checker_event for use at the end of a path:
......
......@@ -818,12 +818,14 @@ diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
case PK_BEFORE_STMT:
{
const gimple *stmt = dst_point.get_stmt ();
if (is_setjmp_call_p (stmt))
const gcall *call = dyn_cast <const gcall *> (stmt);
if (call && is_setjmp_call_p (call))
emission_path->add_event
(new setjmp_event (stmt->location,
dst_node,
dst_point.get_fndecl (),
dst_stack_depth));
dst_stack_depth,
call));
else
emission_path->add_event
(new statement_event (stmt,
......
......@@ -1001,7 +1001,7 @@ exploded_node::on_stmt (exploded_graph &eg,
{
/* This is handled elsewhere. */
}
else if (is_setjmp_call_p (stmt))
else if (is_setjmp_call_p (call))
state->m_region_model->on_setjmp (call, this, &ctxt);
else if (is_longjmp_call_p (call))
{
......@@ -1126,7 +1126,8 @@ public:
return warning_at
(richloc, OPT_Wanalyzer_stale_setjmp_buffer,
"%qs called after enclosing function of %qs has returned",
"longjmp", "setjmp");
get_user_facing_name (m_longjmp_call),
get_user_facing_name (m_setjmp_call));
}
const char *get_kind () const FINAL OVERRIDE
......@@ -1143,10 +1144,10 @@ private:
const gcall *m_longjmp_call;
};
/* Handle LONGJMP_CALL, a call to "longjmp".
/* Handle LONGJMP_CALL, a call to longjmp or siglongjmp.
Attempt to locate where "setjmp" was called on the jmp_buf and build an
exploded_node and exploded_edge to it representing a rewind to that frame,
Attempt to locate where setjmp/sigsetjmp was called on the jmp_buf and build
an exploded_node and exploded_edge to it representing a rewind to that frame,
handling the various kinds of failure that can occur. */
void
......@@ -1174,9 +1175,9 @@ exploded_node::on_longjmp (exploded_graph &eg,
const setjmp_record tmp_setjmp_record = setjmp_sval->get_setjmp_record ();
/* Build a custom enode and eedge for rewinding from the longjmp
call back to the setjmp. */
rewind_info_t rewind_info (tmp_setjmp_record);
/* Build a custom enode and eedge for rewinding from the longjmp/siglongjmp
call back to the setjmp/sigsetjmp. */
rewind_info_t rewind_info (tmp_setjmp_record, longjmp_call);
const gcall *setjmp_call = rewind_info.get_setjmp_call ();
const program_point &setjmp_point = rewind_info.get_setjmp_point ();
......@@ -1217,7 +1218,7 @@ exploded_node::on_longjmp (exploded_graph &eg,
exploded_edge *eedge
= eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
change,
new rewind_info_t (tmp_setjmp_record));
new rewind_info_t (tmp_setjmp_record, longjmp_call));
/* For any diagnostics that were queued here (such as leaks) we want
the checker_path to show the rewinding events after the "final event"
......@@ -1369,7 +1370,7 @@ rewind_info_t::add_events_to_path (checker_path *emission_path,
(new rewind_from_longjmp_event
(&eedge, src_point.get_supernode ()->get_end_location (),
src_point.get_fndecl (),
src_stack_depth));
src_stack_depth, this));
emission_path->add_event
(new rewind_to_setjmp_event
(&eedge, get_setjmp_call ()->location,
......
......@@ -302,13 +302,15 @@ private:
};
/* Extra data for an exploded_edge that represents a rewind from a
longjmp to a setjmp. */
longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */
class rewind_info_t : public exploded_edge::custom_info_t
{
public:
rewind_info_t (const setjmp_record &setjmp_record)
: m_setjmp_record (setjmp_record)
rewind_info_t (const setjmp_record &setjmp_record,
const gcall *longjmp_call)
: m_setjmp_record (setjmp_record),
m_longjmp_call (longjmp_call)
{}
void print (pretty_printer *pp) FINAL OVERRIDE
......@@ -339,6 +341,11 @@ public:
return m_setjmp_record.m_setjmp_call;
}
const gcall *get_longjmp_call () const
{
return m_longjmp_call;
}
const exploded_node *get_enode_origin () const
{
return m_setjmp_record.m_enode;
......@@ -346,6 +353,7 @@ public:
private:
setjmp_record m_setjmp_record;
const gcall *m_longjmp_call;
};
/* Statistics about aspects of an exploded_graph. */
......
......@@ -4480,11 +4480,11 @@ region_model::on_return (const greturn *return_stmt, region_model_context *ctxt)
set_value (get_lvalue (lhs, ctxt), get_rvalue (rhs, ctxt), ctxt);
}
/* Update this model for a call and return of "setjmp" at CALL within ENODE,
using CTXT to report any diagnostics.
/* Update this model for a call and return of setjmp/sigsetjmp at CALL within
ENODE, using CTXT to report any diagnostics.
This is for the initial direct invocation of setjmp (which returns 0),
as opposed to any second return due to longjmp. */
This is for the initial direct invocation of setjmp/sigsetjmp (which returns
0), as opposed to any second return due to longjmp/sigsetjmp. */
void
region_model::on_setjmp (const gcall *call, const exploded_node *enode,
......
......@@ -718,8 +718,8 @@ is_a_helper <poisoned_svalue *>::test (svalue *sval)
namespace ana {
/* A bundle of information recording a setjmp call, corresponding roughly
to a jmp_buf. */
/* A bundle of information recording a setjmp/sigsetjmp call, corresponding
roughly to a jmp_buf. */
struct setjmp_record
{
......@@ -739,8 +739,9 @@ struct setjmp_record
const gcall *m_setjmp_call;
};
/* Concrete subclass of svalue representing setjmp buffers, so that
longjmp can potentially "return" to an entirely different function. */
/* Concrete subclass of svalue representing buffers for setjmp/sigsetjmp,
so that longjmp/siglongjmp can potentially "return" to an entirely
different function. */
class setjmp_svalue : public svalue
{
......
2020-01-27 David Malcolm <dmalcolm@redhat.com>
* gcc.dg/analyzer/sigsetjmp-5.c: New test.
* gcc.dg/analyzer/sigsetjmp-6.c: New test.
2020-01-27 Richard Biener <rguenther@suse.de>
PR testsuite/91171
......
#include <setjmp.h>
#include <stddef.h>
#include "analyzer-decls.h"
static jmp_buf env;
static void inner (void)
{
sigsetjmp (env, 0); /* { dg-message "'sigsetjmp' called here" } */
}
void outer (void)
{
int i;
inner ();
siglongjmp (env, 42); /* { dg-warning "'siglongjmp' called after enclosing function of 'sigsetjmp' has returned" } */
}
#include <setjmp.h>
#include <stddef.h>
#include <stdlib.h>
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
static void inner (void)
{
void *ptr = malloc (1024); /* { dg-message "allocated here" } */
siglongjmp (env, 1); /* { dg-warning "leak of 'ptr'" "warning" } */
/* { dg-message "rewinding from 'siglongjmp' in 'inner'" " event: rewind from" { target *-*-* } .-1 } */
free (ptr);
}
void outer (void)
{
int i;
foo (0);
i = sigsetjmp(env, 0); /* { dg-message "'sigsetjmp' called here" "event: sigsetjmp call" } */
/* { dg-message "to 'sigsetjmp' in 'outer'" "event: rewind to" { target *-*-* } .-1 } */
if (i == 0)
{
foo (1);
inner ();
}
foo (3);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment