+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
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)
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,
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).
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, ...);
{
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.
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. */
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. */
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 ())
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)
{
}
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. */
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)
{
}
};
/* 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
{
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)
{
}
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:
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,
{
/* 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))
{
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
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
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 ();
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"
(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,
};
/* 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
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;
private:
setjmp_record m_setjmp_record;
+ const gcall *m_longjmp_call;
};
/* Statistics about aspects of an exploded_graph. */
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,
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
{
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
--- /dev/null
+#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" } */
+}
--- /dev/null
+#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);
+}