aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/Makefile.in1
-rw-r--r--gcc/analyzer/analyzer.h51
-rw-r--r--gcc/analyzer/call-info.cc162
-rw-r--r--gcc/analyzer/call-info.h83
-rw-r--r--gcc/analyzer/engine.cc271
-rw-r--r--gcc/analyzer/exploded-graph.h62
-rw-r--r--gcc/analyzer/program-state.cc6
-rw-r--r--gcc/analyzer/region-model-impl-calls.cc176
-rw-r--r--gcc/analyzer/region-model.cc28
-rw-r--r--gcc/analyzer/region-model.h36
-rw-r--r--gcc/analyzer/sm-malloc.cc136
-rw-r--r--gcc/analyzer/sm-signal.cc15
-rw-r--r--gcc/analyzer/sm.h5
-rw-r--r--gcc/analyzer/svalue.cc3
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/capacity-2.c8
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/pr99193-1.c2
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/pr99193-3.c2
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/realloc-1.c47
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/realloc-2.c80
19 files changed, 1042 insertions, 132 deletions
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fca..f0c560f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1249,6 +1249,7 @@ ANALYZER_OBJS = \
analyzer/analyzer-pass.o \
analyzer/analyzer-selftests.o \
analyzer/bar-chart.o \
+ analyzer/call-info.o \
analyzer/call-string.o \
analyzer/checker-path.o \
analyzer/complexity.o \
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 05d4751..7ad1081 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -220,6 +220,57 @@ enum access_direction
DIR_WRITE
};
+/* Abstract base class for associating custom data with an
+ exploded_edge, for handling non-standard edges such as
+ rewinding from a longjmp, signal handlers, etc.
+ Also used when "bifurcating" state: splitting the execution
+ path in non-standard ways (e.g. for simulating the various
+ outcomes of "realloc"). */
+
+class custom_edge_info
+{
+public:
+ virtual ~custom_edge_info () {}
+
+ /* Hook for making .dot label more readable. */
+ virtual void print (pretty_printer *pp) const = 0;
+
+ /* Hook for updating MODEL within exploded_path::feasible_p
+ and when handling bifurcation. */
+ virtual bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const = 0;
+
+ virtual void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const = 0;
+};
+
+/* Abstract base class for splitting state.
+
+ Most of the state-management code in the analyzer involves
+ modifying state objects in-place, which assumes a single outcome.
+
+ This class provides an escape hatch to allow for multiple outcomes
+ for such updates e.g. for modelling multiple outcomes from function
+ calls, such as the various outcomes of "realloc". */
+
+class path_context
+{
+public:
+ virtual ~path_context () {}
+
+ /* Hook for clients to split state with a non-standard path.
+ Take ownership of INFO. */
+ virtual void bifurcate (custom_edge_info *info) = 0;
+
+ /* Hook for clients to terminate the standard path. */
+ virtual void terminate_path () = 0;
+
+ /* Hook for clients to determine if the standard path has been
+ terminated. */
+ virtual bool terminate_path_p () const = 0;
+};
+
} // namespace ana
extern bool is_special_named_call_p (const gcall *call, const char *funcname,
diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc
new file mode 100644
index 0000000..1d44cb8
--- /dev/null
+++ b/gcc/analyzer/call-info.cc
@@ -0,0 +1,162 @@
+/* Subclasses of custom_edge_info for describing outcomes of function calls.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "diagnostic-core.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-pretty-print.h"
+#include "tristate.h"
+#include "bitmap.h"
+#include "selftest.h"
+#include "function.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/region-model-reachability.h"
+#include "analyzer/analyzer-selftests.h"
+#include "analyzer/program-state.h"
+#include "diagnostic-path.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/diagnostic-manager.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/call-info.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* class call_info : public custom_eedge_info_t. */
+
+/* Implementation of custom_edge_info::print vfunc for call_info:
+ use get_desc to get a label_text, and print it to PP. */
+
+void
+call_info::print (pretty_printer *pp) const
+{
+ label_text desc (get_desc (pp_show_color (pp)));
+ pp_string (pp, desc.m_buffer);
+ desc.maybe_free ();
+}
+
+/* Implementation of custom_edge_info::add_events_to_path vfunc for
+ call_info: add a custom_event using call_info::get_desc as its
+ description. */
+
+void
+call_info::add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const
+{
+ class call_event : public custom_event
+ {
+ public:
+ call_event (location_t loc, tree fndecl, int depth,
+ const call_info *call_info)
+ : custom_event (loc, fndecl, depth),
+ m_call_info (call_info)
+ {}
+
+ label_text get_desc (bool can_colorize) const
+ {
+ return m_call_info->get_desc (can_colorize);
+ }
+
+ private:
+ const call_info *m_call_info;
+ };
+
+ const exploded_node *src_node = eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ tree caller_fndecl = src_point.get_fndecl ();
+ const int stack_depth = src_point.get_stack_depth ();
+
+ emission_path->add_event (new call_event (get_call_stmt ()->location,
+ caller_fndecl,
+ stack_depth,
+ this));
+}
+
+/* Recreate a call_details instance from this call_info. */
+
+call_details
+call_info::get_call_details (region_model *model,
+ region_model_context *ctxt) const
+{
+ return call_details (m_call_stmt, model, ctxt);
+}
+
+/* call_info's ctor.
+
+ The call_info instance will outlive the call_details instance;
+ call_details instances are typically created on the stack. */
+
+call_info::call_info (const call_details &cd)
+: m_call_stmt (cd.get_call_stmt ()),
+ m_fndecl (cd.get_fndecl_for_call ())
+{
+ gcc_assert (m_fndecl);
+}
+
+/* class success_call_info : public call_info. */
+
+/* Implementation of call_info::get_desc vfunc for success_call_info. */
+
+label_text
+success_call_info::get_desc (bool can_colorize) const
+{
+ return make_label_text (can_colorize, "when %qE succeeds", get_fndecl ());
+}
+
+/* class failed_call_info : public call_info. */
+
+/* Implementation of call_info::get_desc vfunc for failed_call_info. */
+
+label_text
+failed_call_info::get_desc (bool can_colorize) const
+{
+ return make_label_text (can_colorize, "when %qE fails", get_fndecl ());
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h
new file mode 100644
index 0000000..369d217
--- /dev/null
+++ b/gcc/analyzer/call-info.h
@@ -0,0 +1,83 @@
+/* Subclasses of custom_edge_info for describing outcomes of function calls.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_CALL_INFO_H
+#define GCC_ANALYZER_CALL_INFO_H
+
+namespace ana {
+
+/* Subclass of custom_edge_info for an outcome of a call.
+ This is still abstract; the update_model and get_desc vfuncs must be
+ implemented. */
+
+class call_info : public custom_edge_info
+{
+public:
+ void print (pretty_printer *pp) const FINAL OVERRIDE;
+ void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const FINAL OVERRIDE;
+
+ const gcall *get_call_stmt () const { return m_call_stmt; }
+ tree get_fndecl () const { return m_fndecl; }
+
+ virtual label_text get_desc (bool can_colorize) const = 0;
+
+ call_details get_call_details (region_model *model,
+ region_model_context *ctxt) const;
+
+protected:
+ call_info (const call_details &cd);
+
+private:
+ const gcall *m_call_stmt;
+ tree m_fndecl;
+};
+
+/* Subclass of call_info for a "success" outcome of a call,
+ adding a "when `FNDECL' succeeds" message.
+ This is still abstract: the custom_edge_info::update_model vfunc
+ must be implemented. */
+
+class success_call_info : public call_info
+{
+public:
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+protected:
+ success_call_info (const call_details &cd) : call_info (cd) {}
+};
+
+/* Subclass of call_info for a "failure" outcome of a call,
+ adding a "when `FNDECL' fails" message.
+ This is still abstract: the custom_edge_info::update_model vfunc
+ must be implemented. */
+
+class failed_call_info : public call_info
+{
+public:
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+protected:
+ failed_call_info (const call_details &cd) : call_info (cd) {}
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_CALL_INFO_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 9c604d1..24f0931 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -62,9 +62,11 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/checker-path.h"
#include "analyzer/state-purge.h"
#include "analyzer/bar-chart.h"
+#include "analyzer/call-info.h"
#include <zlib.h>
#include "plugin.h"
#include "target.h"
+#include <memory>
/* For an overview, see gcc/doc/analyzer.texi. */
@@ -80,6 +82,7 @@ impl_region_model_context (exploded_graph &eg,
const program_state *old_state,
program_state *new_state,
uncertainty_t *uncertainty,
+ path_context *path_ctxt,
const gimple *stmt,
stmt_finder *stmt_finder)
: m_eg (&eg), m_logger (eg.get_logger ()),
@@ -89,7 +92,8 @@ impl_region_model_context (exploded_graph &eg,
m_stmt (stmt),
m_stmt_finder (stmt_finder),
m_ext_state (eg.get_ext_state ()),
- m_uncertainty (uncertainty)
+ m_uncertainty (uncertainty),
+ m_path_ctxt (path_ctxt)
{
}
@@ -104,7 +108,8 @@ impl_region_model_context (program_state *state,
m_stmt (NULL),
m_stmt_finder (NULL),
m_ext_state (ext_state),
- m_uncertainty (uncertainty)
+ m_uncertainty (uncertainty),
+ m_path_ctxt (NULL)
{
}
@@ -183,6 +188,37 @@ impl_region_model_context::purge_state_involving (const svalue *sval)
smap->purge_state_involving (sval, m_ext_state);
}
+void
+impl_region_model_context::bifurcate (custom_edge_info *info)
+{
+ if (m_path_ctxt)
+ m_path_ctxt->bifurcate (info);
+ else
+ delete info;
+}
+
+void
+impl_region_model_context::terminate_path ()
+{
+ if (m_path_ctxt)
+ return m_path_ctxt->terminate_path ();
+}
+
+bool
+impl_region_model_context::get_malloc_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx)
+{
+ unsigned malloc_sm_idx;
+ if (!m_ext_state.get_sm_idx_by_name ("malloc", &malloc_sm_idx))
+ return false;
+
+ *out_smap = m_new_state->m_checker_states[malloc_sm_idx];
+ *out_sm = &m_ext_state.get_sm (malloc_sm_idx);
+ *out_sm_idx = malloc_sm_idx;
+ return true;
+}
+
/* struct setjmp_record. */
int
@@ -237,12 +273,14 @@ public:
program_state *new_state,
const sm_state_map *old_smap,
sm_state_map *new_smap,
+ path_context *path_ctxt,
stmt_finder *stmt_finder = NULL)
: sm_context (sm_idx, sm),
m_logger (eg.get_logger ()),
m_eg (eg), m_enode_for_diag (enode_for_diag),
m_old_state (old_state), m_new_state (new_state),
m_old_smap (old_smap), m_new_smap (new_smap),
+ m_path_ctxt (path_ctxt),
m_stmt_finder (stmt_finder)
{
}
@@ -252,7 +290,7 @@ public:
tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE
{
impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+ (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/,
NULL, call);
region_model *model = m_new_state->m_region_model;
return model->get_fndecl_for_call (call, &old_ctxt);
@@ -292,7 +330,7 @@ public:
LOG_FUNC (logger);
impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
m_old_state, m_new_state,
- NULL,
+ NULL, NULL,
stmt);
const svalue *var_new_sval
= m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
@@ -320,12 +358,12 @@ public:
logger * const logger = get_logger ();
LOG_FUNC (logger);
impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+ (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/,
NULL, stmt);
impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
m_old_state, m_new_state,
- NULL,
+ NULL, NULL,
stmt);
const svalue *origin_new_sval
= m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
@@ -353,7 +391,7 @@ public:
LOG_FUNC (get_logger ());
gcc_assert (d); // take ownership
impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL);
+ (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL);
const svalue *var_old_sval
= m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
@@ -418,7 +456,7 @@ public:
if (!assign_stmt)
return NULL_TREE;
impl_region_model_context old_ctxt
- (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt);
+ (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, stmt);
if (const svalue *sval
= m_new_state->m_region_model->get_gassign_result (assign_stmt,
&old_ctxt))
@@ -428,6 +466,11 @@ public:
return NULL_TREE;
}
+ path_context *get_path_context () const FINAL OVERRIDE
+ {
+ return m_path_ctxt;
+ }
+
log_user m_logger;
exploded_graph &m_eg;
exploded_node *m_enode_for_diag;
@@ -435,6 +478,7 @@ public:
program_state *m_new_state;
const sm_state_map *m_old_smap;
sm_state_map *m_new_smap;
+ path_context *m_path_ctxt;
stmt_finder *m_stmt_finder;
};
@@ -751,9 +795,13 @@ impl_region_model_context::on_condition (const svalue *lhs,
impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
m_old_state, m_new_state,
m_old_state->m_checker_states[sm_idx],
- m_new_state->m_checker_states[sm_idx]);
+ m_new_state->m_checker_states[sm_idx],
+ m_path_ctxt);
sm.on_condition (&sm_ctxt,
- m_enode_for_diag->get_supernode (), m_stmt,
+ (m_enode_for_diag
+ ? m_enode_for_diag->get_supernode ()
+ : NULL),
+ m_stmt,
lhs, op, rhs);
}
}
@@ -773,7 +821,8 @@ impl_region_model_context::on_phi (const gphi *phi, tree rhs)
impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
m_old_state, m_new_state,
m_old_state->m_checker_states[sm_idx],
- m_new_state->m_checker_states[sm_idx]);
+ m_new_state->m_checker_states[sm_idx],
+ m_path_ctxt);
sm.on_phi (&sm_ctxt, m_enode_for_diag->get_supernode (), phi, rhs);
}
}
@@ -1190,7 +1239,8 @@ exploded_node::on_stmt (exploded_graph &eg,
const supernode *snode,
const gimple *stmt,
program_state *state,
- uncertainty_t *uncertainty)
+ uncertainty_t *uncertainty,
+ path_context *path_ctxt)
{
logger *logger = eg.get_logger ();
LOG_SCOPE (logger);
@@ -1215,7 +1265,7 @@ exploded_node::on_stmt (exploded_graph &eg,
impl_region_model_context ctxt (eg, this,
&old_state, state, uncertainty,
- stmt);
+ path_ctxt, stmt);
bool unknown_side_effects = false;
bool terminate_path = false;
@@ -1235,13 +1285,16 @@ exploded_node::on_stmt (exploded_graph &eg,
= old_state.m_checker_states[sm_idx];
sm_state_map *new_smap = state->m_checker_states[sm_idx];
impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state,
- old_smap, new_smap);
+ old_smap, new_smap, path_ctxt);
/* Allow the state_machine to handle the stmt. */
if (sm.on_stmt (&sm_ctxt, snode, stmt))
unknown_side_effects = false;
}
+ if (path_ctxt->terminate_path_p ())
+ return on_stmt_flags::terminate_path ();
+
on_stmt_post (stmt, state, unknown_side_effects, &ctxt);
return on_stmt_flags ();
@@ -1592,7 +1645,7 @@ exploded_node::detect_leaks (exploded_graph &eg)
uncertainty_t uncertainty;
impl_region_model_context ctxt (eg, this,
- &old_state, &new_state, &uncertainty,
+ &old_state, &new_state, &uncertainty, NULL,
get_stmt ());
const svalue *result = NULL;
new_state.m_region_model->pop_frame (NULL, &result, &ctxt);
@@ -1627,27 +1680,30 @@ exploded_node::dump_succs_and_preds (FILE *outf) const
}
}
-/* class dynamic_call_info_t : public exploded_edge::custom_info_t. */
+/* class dynamic_call_info_t : public custom_edge_info. */
-/* Implementation of exploded_edge::custom_info_t::update_model vfunc
+/* Implementation of custom_edge_info::update_model vfunc
for dynamic_call_info_t.
Update state for the dynamically discorverd calls */
-void
+bool
dynamic_call_info_t::update_model (region_model *model,
- const exploded_edge &eedge)
+ const exploded_edge *eedge,
+ region_model_context *) const
{
- const program_state &dest_state = eedge.m_dest->get_state ();
+ gcc_assert (eedge);
+ const program_state &dest_state = eedge->m_dest->get_state ();
*model = *dest_state.m_region_model;
+ return true;
}
-/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc
+/* Implementation of custom_edge_info::add_events_to_path vfunc
for dynamic_call_info_t. */
void
dynamic_call_info_t::add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge)
+ const exploded_edge &eedge) const
{
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
@@ -1671,21 +1727,23 @@ dynamic_call_info_t::add_events_to_path (checker_path *emission_path,
}
-/* class rewind_info_t : public exploded_edge::custom_info_t. */
+/* class rewind_info_t : public custom_edge_info. */
-/* Implementation of exploded_edge::custom_info_t::update_model vfunc
+/* Implementation of custom_edge_info::update_model vfunc
for rewind_info_t.
Update state for the special-case of a rewind of a longjmp
to a setjmp (which doesn't have a superedge, but does affect
state). */
-void
+bool
rewind_info_t::update_model (region_model *model,
- const exploded_edge &eedge)
+ const exploded_edge *eedge,
+ region_model_context *) const
{
- const program_point &longjmp_point = eedge.m_src->get_point ();
- const program_point &setjmp_point = eedge.m_dest->get_point ();
+ gcc_assert (eedge);
+ const program_point &longjmp_point = eedge->m_src->get_point ();
+ const program_point &setjmp_point = eedge->m_dest->get_point ();
gcc_assert (longjmp_point.get_stack_depth ()
>= setjmp_point.get_stack_depth ());
@@ -1693,14 +1751,15 @@ rewind_info_t::update_model (region_model *model,
model->on_longjmp (get_longjmp_call (),
get_setjmp_call (),
setjmp_point.get_stack_depth (), NULL);
+ return true;
}
-/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc
+/* Implementation of custom_edge_info::add_events_to_path vfunc
for rewind_info_t. */
void
rewind_info_t::add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge)
+ const exploded_edge &eedge) const
{
const exploded_node *src_node = eedge.m_src;
const program_point &src_point = src_node->get_point ();
@@ -1727,7 +1786,7 @@ rewind_info_t::add_events_to_path (checker_path *emission_path,
exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
const superedge *sedge,
- custom_info_t *custom_info)
+ custom_edge_info *custom_info)
: dedge<eg_traits> (src, dest), m_sedge (sedge),
m_custom_info (custom_info)
{
@@ -2432,7 +2491,7 @@ exploded_graph::get_or_create_node (const program_point &point,
exploded_edge *
exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
const superedge *sedge,
- exploded_edge::custom_info_t *custom_info)
+ custom_edge_info *custom_info)
{
if (get_logger ())
get_logger ()->log ("creating edge EN: %i -> EN: %i",
@@ -2866,7 +2925,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode)
uncertainty_t uncertainty;
impl_region_model_context ctxt (*this, iter_enode,
&state, next_state,
- &uncertainty, NULL);
+ &uncertainty, NULL, NULL);
const cfg_superedge *last_cfg_superedge
= iter_sedge->dyn_cast_cfg_superedge ();
if (last_cfg_superedge)
@@ -3095,6 +3154,72 @@ exploded_graph::maybe_create_dynamic_call (const gcall *call,
return false;
}
+/* Subclass of path_context for use within exploded_graph::process_node,
+ so that we can split states e.g. at "realloc" calls. */
+
+class impl_path_context : public path_context
+{
+public:
+ impl_path_context (const program_state *cur_state)
+ : m_cur_state (cur_state),
+ m_terminate_path (false)
+ {
+ }
+
+ bool bifurcation_p () const
+ {
+ return m_custom_eedge_infos.length () > 0;
+ }
+
+ const program_state &get_state_at_bifurcation () const
+ {
+ gcc_assert (m_state_at_bifurcation);
+ return *m_state_at_bifurcation;
+ }
+
+ void
+ bifurcate (custom_edge_info *info) FINAL OVERRIDE
+ {
+ if (m_state_at_bifurcation)
+ /* Verify that the state at bifurcation is consistent when we
+ split into multiple out-edges. */
+ gcc_assert (*m_state_at_bifurcation == *m_cur_state);
+ else
+ /* Take a copy of the cur_state at the moment when bifurcation
+ happens. */
+ m_state_at_bifurcation
+ = std::unique_ptr<program_state> (new program_state (*m_cur_state));
+
+ /* Take ownership of INFO. */
+ m_custom_eedge_infos.safe_push (info);
+ }
+
+ void terminate_path () FINAL OVERRIDE
+ {
+ m_terminate_path = true;
+ }
+
+ bool terminate_path_p () const FINAL OVERRIDE
+ {
+ return m_terminate_path;
+ }
+
+ const vec<custom_edge_info *> & get_custom_eedge_infos ()
+ {
+ return m_custom_eedge_infos;
+ }
+
+private:
+ const program_state *m_cur_state;
+
+ /* Lazily-created copy of the state before the split. */
+ std::unique_ptr<program_state> m_state_at_bifurcation;
+
+ auto_vec <custom_edge_info *> m_custom_eedge_infos;
+
+ bool m_terminate_path;
+};
+
/* The core of exploded_graph::process_worklist (the main analysis loop),
handling one node in the worklist.
@@ -3150,7 +3275,7 @@ exploded_graph::process_node (exploded_node *node)
{
impl_region_model_context ctxt (*this, node,
&state, &next_state,
- &uncertainty, NULL);
+ &uncertainty, NULL, NULL);
const cfg_superedge *last_cfg_superedge
= point.get_from_edge ()->dyn_cast_cfg_superedge ();
if (last_cfg_superedge)
@@ -3188,6 +3313,9 @@ exploded_graph::process_node (exploded_node *node)
the sm-state-change occurs on an edge where the src enode has
exactly one stmt, the one that caused the change. */
program_state next_state (state);
+
+ impl_path_context path_ctxt (&next_state);
+
uncertainty_t uncertainty;
const supernode *snode = point.get_supernode ();
unsigned stmt_idx;
@@ -3210,7 +3338,8 @@ exploded_graph::process_node (exploded_node *node)
/* Process the stmt. */
exploded_node::on_stmt_flags flags
- = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty);
+ = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty,
+ &path_ctxt);
node->m_num_processed_stmts++;
/* If flags.m_terminate_path, stop analyzing; any nodes/edges
@@ -3222,7 +3351,7 @@ exploded_graph::process_node (exploded_node *node)
{
impl_region_model_context ctxt (*this, node,
&old_state, &next_state,
- &uncertainty, stmt);
+ &uncertainty, NULL, stmt);
program_state::detect_leaks (old_state, next_state, NULL,
get_ext_state (), &ctxt);
}
@@ -3238,7 +3367,9 @@ exploded_graph::process_node (exploded_node *node)
&uncertainty);
if (flag_analyzer_fine_grained
- || state_change_requires_new_enode_p (old_state, next_state))
+ || state_change_requires_new_enode_p (old_state, next_state)
+ || path_ctxt.bifurcation_p ()
+ || path_ctxt.terminate_path_p ())
{
program_point split_point
= program_point::before_stmt (point.get_supernode (),
@@ -3282,9 +3413,66 @@ exploded_graph::process_node (exploded_node *node)
point.get_call_string ())
: program_point::after_supernode (point.get_supernode (),
point.get_call_string ()));
- exploded_node *next = get_or_create_node (next_point, next_state, node);
- if (next)
- add_edge (node, next, NULL);
+ if (path_ctxt.terminate_path_p ())
+ {
+ if (logger)
+ logger->log ("not adding node: terminating path");
+ }
+ else
+ {
+ exploded_node *next
+ = get_or_create_node (next_point, next_state, node);
+ if (next)
+ add_edge (node, next, NULL);
+ }
+
+ /* If we have custom edge infos, "bifurcate" the state
+ accordingly, potentially creating a new state/enode/eedge
+ instances. For example, to handle a "realloc" call, we
+ might split into 3 states, for the "failure",
+ "resizing in place", and "moving to a new buffer" cases. */
+ for (auto edge_info : path_ctxt.get_custom_eedge_infos ())
+ {
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("bifurcating for edge: ");
+ edge_info->print (logger->get_printer ());
+ logger->end_log_line ();
+ }
+ program_state bifurcated_new_state
+ (path_ctxt.get_state_at_bifurcation ());
+
+ /* Apply edge_info to state. */
+ impl_region_model_context
+ bifurcation_ctxt (*this,
+ NULL, // enode_for_diag
+ &path_ctxt.get_state_at_bifurcation (),
+ &bifurcated_new_state,
+ NULL, // uncertainty_t *uncertainty
+ NULL, // path_context *path_ctxt
+ stmt);
+ if (edge_info->update_model (bifurcated_new_state.m_region_model,
+ NULL, /* no exploded_edge yet. */
+ &bifurcation_ctxt))
+ {
+ exploded_node *next2
+ = get_or_create_node (next_point, bifurcated_new_state, node);
+ if (next2)
+ {
+ /* Take ownership of edge_info. */
+ add_edge (node, next2, NULL, edge_info);
+ }
+ else
+ delete edge_info;
+ }
+ else
+ {
+ if (logger)
+ logger->log ("infeasible state, not adding node");
+ delete edge_info;
+ }
+ }
}
break;
case PK_AFTER_SUPERNODE:
@@ -3351,6 +3539,7 @@ exploded_graph::process_node (exploded_node *node)
&state,
&next_state,
&uncertainty,
+ NULL,
point.get_stmt());
region_model *model = state.m_region_model;
@@ -3968,7 +4157,7 @@ feasibility_state::maybe_update_for_edge (logger *logger,
}
else if (eedge->m_custom_info)
{
- eedge->m_custom_info->update_model (&m_model, *eedge);
+ eedge->m_custom_info->update_model (&m_model, eedge, NULL);
}
}
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 6890e84..b9c1767 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -37,6 +37,7 @@ class impl_region_model_context : public region_model_context
const program_state *old_state,
program_state *new_state,
uncertainty_t *uncertainty,
+ path_context *path_ctxt,
const gimple *stmt,
stmt_finder *stmt_finder = NULL);
@@ -76,6 +77,16 @@ class impl_region_model_context : public region_model_context
void purge_state_involving (const svalue *sval) FINAL OVERRIDE;
+ void bifurcate (custom_edge_info *info) FINAL OVERRIDE;
+ void terminate_path () FINAL OVERRIDE;
+ const extrinsic_state *get_ext_state () const FINAL OVERRIDE
+ {
+ return &m_ext_state;
+ }
+ bool get_malloc_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx) FINAL OVERRIDE;
+
exploded_graph *m_eg;
log_user m_logger;
exploded_node *m_enode_for_diag;
@@ -85,6 +96,7 @@ class impl_region_model_context : public region_model_context
stmt_finder *m_stmt_finder;
const extrinsic_state &m_ext_state;
uncertainty_t *m_uncertainty;
+ path_context *m_path_ctxt;
};
/* A <program_point, program_state> pair, used internally by
@@ -224,7 +236,8 @@ class exploded_node : public dnode<eg_traits>
const supernode *snode,
const gimple *stmt,
program_state *state,
- uncertainty_t *uncertainty);
+ uncertainty_t *uncertainty,
+ path_context *path_ctxt);
void on_stmt_pre (exploded_graph &eg,
const gimple *stmt,
program_state *state,
@@ -319,28 +332,9 @@ public:
class exploded_edge : public dedge<eg_traits>
{
public:
- /* Abstract base class for associating custom data with an
- exploded_edge, for handling non-standard edges such as
- rewinding from a longjmp, signal handlers, etc. */
- class custom_info_t
- {
- public:
- virtual ~custom_info_t () {}
-
- /* Hook for making .dot label more readable . */
- virtual void print (pretty_printer *pp) = 0;
-
- /* Hook for updating MODEL within exploded_path::feasible_p. */
- virtual void update_model (region_model *model,
- const exploded_edge &eedge) = 0;
-
- virtual void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) = 0;
- };
-
exploded_edge (exploded_node *src, exploded_node *dest,
const superedge *sedge,
- custom_info_t *custom_info);
+ custom_edge_info *custom_info);
~exploded_edge ();
void dump_dot (graphviz_out *gv, const dump_args_t &args)
const FINAL OVERRIDE;
@@ -356,7 +350,7 @@ class exploded_edge : public dedge<eg_traits>
a signal is delivered to a signal-handler.
Owned by this class. */
- custom_info_t *m_custom_info;
+ custom_edge_info *m_custom_info;
private:
DISABLE_COPY_AND_ASSIGN (exploded_edge);
@@ -365,7 +359,7 @@ private:
/* Extra data for an exploded_edge that represents dynamic call info ( calls
that doesn't have an underlying superedge representing the call ). */
-class dynamic_call_info_t : public exploded_edge::custom_info_t
+class dynamic_call_info_t : public custom_edge_info
{
public:
dynamic_call_info_t (const gcall *dynamic_call,
@@ -374,7 +368,7 @@ public:
m_is_returning_call (is_returning_call)
{}
- void print (pretty_printer *pp) FINAL OVERRIDE
+ void print (pretty_printer *pp) const FINAL OVERRIDE
{
if (m_is_returning_call)
pp_string (pp, "dynamic_return");
@@ -382,11 +376,12 @@ public:
pp_string (pp, "dynamic_call");
}
- void update_model (region_model *model,
- const exploded_edge &eedge) FINAL OVERRIDE;
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const FINAL OVERRIDE;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) FINAL OVERRIDE;
+ const exploded_edge &eedge) const FINAL OVERRIDE;
private:
const gcall *m_dynamic_call;
const bool m_is_returning_call;
@@ -396,7 +391,7 @@ private:
/* Extra data for an exploded_edge that represents a rewind from a
longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */
-class rewind_info_t : public exploded_edge::custom_info_t
+class rewind_info_t : public custom_edge_info
{
public:
rewind_info_t (const setjmp_record &setjmp_record,
@@ -405,16 +400,17 @@ public:
m_longjmp_call (longjmp_call)
{}
- void print (pretty_printer *pp) FINAL OVERRIDE
+ void print (pretty_printer *pp) const FINAL OVERRIDE
{
pp_string (pp, "rewind");
}
- void update_model (region_model *model,
- const exploded_edge &eedge) FINAL OVERRIDE;
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *ctxt) const FINAL OVERRIDE;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) FINAL OVERRIDE;
+ const exploded_edge &eedge) const FINAL OVERRIDE;
const program_point &get_setjmp_point () const
{
@@ -829,7 +825,7 @@ public:
exploded_node *enode_for_diag);
exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
const superedge *sedge,
- exploded_edge::custom_info_t *custom = NULL);
+ custom_edge_info *custom = NULL);
per_program_point_data *
get_or_create_per_program_point_data (const program_point &);
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index ea53c61..c1ff0d8 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1013,7 +1013,7 @@ program_state::on_edge (exploded_graph &eg,
impl_region_model_context ctxt (eg, enode,
&enode->get_state (),
this,
- uncertainty,
+ uncertainty, NULL,
last_stmt);
if (!m_region_model->maybe_update_for_edge (*succ,
last_stmt,
@@ -1052,6 +1052,7 @@ program_state::push_call (exploded_graph &eg,
&enode->get_state (),
this,
uncertainty,
+ NULL,
last_stmt);
m_region_model->update_for_gcall (call_stmt, &ctxt);
}
@@ -1074,6 +1075,7 @@ program_state::returning_call (exploded_graph &eg,
&enode->get_state (),
this,
uncertainty,
+ NULL,
last_stmt);
m_region_model->update_for_return_gcall (call_stmt, &ctxt);
}
@@ -1152,7 +1154,7 @@ program_state::prune_for_point (exploded_graph &eg,
impl_region_model_context ctxt (eg, enode_for_diag,
this,
&new_state,
- uncertainty,
+ uncertainty, NULL,
point.get_stmt ());
detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt);
}
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index e5a6cb2..875719f 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -56,6 +56,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/program-point.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
+#include "analyzer/call-info.h"
#include "gimple-pretty-print.h"
#if ENABLE_ANALYZER
@@ -158,6 +159,15 @@ call_details::get_arg_string_literal (unsigned idx) const
return NULL;
}
+/* Attempt to get the fndecl used at this call, if known, or NULL_TREE
+ otherwise. */
+
+tree
+call_details::get_fndecl_for_call () const
+{
+ return m_model->get_fndecl_for_call (m_call, m_ctxt);
+}
+
/* Dump a multiline representation of this call to PP. */
void
@@ -486,15 +496,169 @@ region_model::impl_call_operator_delete (const call_details &cd)
}
}
-/* Handle the on_call_pre part of "realloc". */
+/* Handle the on_call_post part of "realloc":
+
+ void *realloc(void *ptr, size_t size);
+
+ realloc(3) is awkward, since it has various different outcomes
+ that are best modelled as separate exploded nodes/edges.
+
+ We first check for sm-state, in
+ malloc_state_machine::on_realloc_call, so that we
+ can complain about issues such as realloc of a non-heap
+ pointer, and terminate the path for such cases (and issue
+ the complaints at the call's exploded node).
+
+ Assuming that these checks pass, we split the path here into
+ three special cases (and terminate the "standard" path):
+ (A) failure, returning NULL
+ (B) success, growing the buffer in-place without moving it
+ (C) success, allocating a new buffer, copying the content
+ of the old buffer to it, and freeing the old buffer.
+
+ Each of these has a custom_edge_info subclass, which updates
+ the region_model and sm-state of the destination state. */
void
-region_model::impl_call_realloc (const call_details &)
+region_model::impl_call_realloc (const call_details &cd)
{
- /* Currently we don't support bifurcating state, so there's no good
- way to implement realloc(3).
- For now, malloc_state_machine::on_realloc_call has a minimal
- implementation to suppress false positives. */
+ /* Three custom subclasses of custom_edge_info, for handling the various
+ outcomes of "realloc". */
+
+ /* Concrete custom_edge_info: a realloc call that fails, returning NULL. */
+ class failure : public failed_call_info
+ {
+ public:
+ failure (const call_details &cd)
+ : failed_call_info (cd)
+ {
+ }
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const FINAL OVERRIDE
+ {
+ /* Return NULL; everything else is unchanged. */
+ const call_details cd (get_call_details (model, ctxt));
+ const svalue *zero
+ = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+ model->set_value (cd.get_lhs_region (),
+ zero,
+ cd.get_ctxt ());
+ return true;
+ }
+ };
+
+ /* Concrete custom_edge_info: a realloc call that succeeds, growing
+ the existing buffer without moving it. */
+ class success_no_move : public call_info
+ {
+ public:
+ success_no_move (const call_details &cd)
+ : call_info (cd)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+ {
+ return make_label_text (can_colorize,
+ "when %qE succeeds, without moving buffer",
+ get_fndecl ());
+ }
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const FINAL OVERRIDE
+ {
+ /* Update size of buffer and return the ptr unchanged. */
+ const call_details cd (get_call_details (model, ctxt));
+ const svalue *ptr_sval = cd.get_arg_svalue (0);
+ const svalue *size_sval = cd.get_arg_svalue (1);
+ if (const region *buffer_reg = ptr_sval->maybe_get_region ())
+ model->set_dynamic_extents (buffer_reg, size_sval);
+ model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ());
+ const svalue *zero
+ = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+ return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ());
+ }
+ };
+
+ /* Concrete custom_edge_info: a realloc call that succeeds, freeing
+ the existing buffer and moving the content to a freshly allocated
+ buffer. */
+ class success_with_move : public call_info
+ {
+ public:
+ success_with_move (const call_details &cd)
+ : call_info (cd)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE
+ {
+ return make_label_text (can_colorize,
+ "when %qE succeeds, moving buffer",
+ get_fndecl ());
+ }
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const FINAL OVERRIDE
+ {
+ const call_details cd (get_call_details (model, ctxt));
+ const svalue *old_ptr_sval = cd.get_arg_svalue (0);
+ const svalue *new_size_sval = cd.get_arg_svalue (1);
+
+ /* Create the new region. */
+ const region *new_reg
+ = model->create_region_for_heap_alloc (new_size_sval);
+ const svalue *new_ptr_sval
+ = model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+ if (cd.get_lhs_type ())
+ cd.maybe_set_lhs (new_ptr_sval);
+
+ if (const region *freed_reg = old_ptr_sval->maybe_get_region ())
+ {
+ /* Copy the data. */
+ const svalue *old_size_sval = model->get_dynamic_extents (freed_reg);
+ if (old_size_sval)
+ {
+ const region *sized_old_reg
+ = model->m_mgr->get_sized_region (freed_reg, NULL,
+ old_size_sval);
+ const svalue *buffer_content_sval
+ = model->get_store_value (sized_old_reg, cd.get_ctxt ());
+ model->set_value (new_reg, buffer_content_sval, cd.get_ctxt ());
+ }
+
+ /* Free the old region, so that pointers to the old buffer become
+ invalid. */
+
+ /* If the ptr points to an underlying heap region, delete it,
+ poisoning pointers. */
+ model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
+ model->m_dynamic_extents.remove (freed_reg);
+ }
+
+ /* Update the sm-state: mark the old_ptr_sval as "freed",
+ and the new_ptr_sval as "nonnull". */
+ model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval);
+
+ const svalue *zero
+ = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+ return model->add_constraint (new_ptr_sval, NE_EXPR, zero,
+ cd.get_ctxt ());
+ }
+ };
+
+ /* Body of region_model::impl_call_realloc. */
+
+ if (cd.get_ctxt ())
+ {
+ cd.get_ctxt ()->bifurcate (new failure (cd));
+ cd.get_ctxt ()->bifurcate (new success_no_move (cd));
+ cd.get_ctxt ()->bifurcate (new success_with_move (cd));
+ cd.get_ctxt ()->terminate_path ();
+ }
}
/* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk". */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 787f2ed..3bfc4ba 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1132,7 +1132,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
return false;
break;
case BUILT_IN_REALLOC:
- impl_call_realloc (cd);
return false;
case BUILT_IN_STRCPY:
case BUILT_IN_STRCPY_CHK:
@@ -1276,9 +1275,9 @@ region_model::on_call_post (const gcall *call,
{
if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
{
+ call_details cd (call, this, ctxt);
if (is_named_call_p (callee_fndecl, "free", call, 1))
{
- call_details cd (call, this, ctxt);
impl_call_free (cd);
return;
}
@@ -1286,7 +1285,6 @@ region_model::on_call_post (const gcall *call,
|| is_named_call_p (callee_fndecl, "operator delete", call, 2)
|| is_named_call_p (callee_fndecl, "operator delete []", call, 1))
{
- call_details cd (call, this, ctxt);
impl_call_operator_delete (cd);
return;
}
@@ -1294,10 +1292,19 @@ region_model::on_call_post (const gcall *call,
__attribute__((malloc(FOO)))? */
if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl)))
{
- call_details cd (call, this, ctxt);
impl_deallocation_call (cd);
return;
}
+ if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
+ && gimple_builtin_call_types_compatible_p (call, callee_fndecl))
+ switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
+ {
+ default:
+ break;
+ case BUILT_IN_REALLOC:
+ impl_call_realloc (cd);
+ return;
+ }
}
if (unknown_side_effects)
@@ -3765,6 +3772,19 @@ region_model::unset_dynamic_extents (const region *reg)
m_dynamic_extents.remove (reg);
}
+/* class noop_region_model_context : public region_model_context. */
+
+void
+noop_region_model_context::bifurcate (custom_edge_info *info)
+{
+ delete info;
+}
+
+void
+noop_region_model_context::terminate_path ()
+{
+}
+
/* struct model_merger. */
/* Dump a multiline representation of this merger to PP. */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index f2c82b0..5fabf78 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -487,11 +487,15 @@ public:
unsigned num_args () const;
+ const gcall *get_call_stmt () const { return m_call; }
+
tree get_arg_tree (unsigned idx) const;
tree get_arg_type (unsigned idx) const;
const svalue *get_arg_svalue (unsigned idx) const;
const char *get_arg_string_literal (unsigned idx) const;
+ tree get_fndecl_for_call () const;
+
void dump_to_pp (pretty_printer *pp, bool simple) const;
void dump (bool simple) const;
@@ -732,6 +736,11 @@ class region_model
const svalue *get_capacity (const region *reg) const;
+ /* Implemented in sm-malloc.cc */
+ void on_realloc_with_move (const call_details &cd,
+ const svalue *old_ptr_sval,
+ const svalue *new_ptr_sval);
+
private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -867,6 +876,21 @@ class region_model_context
/* Hook for clients to purge state involving SVAL. */
virtual void purge_state_involving (const svalue *sval) = 0;
+
+ /* Hook for clients to split state with a non-standard path.
+ Take ownership of INFO. */
+ virtual void bifurcate (custom_edge_info *info) = 0;
+
+ /* Hook for clients to terminate the standard path. */
+ virtual void terminate_path () = 0;
+
+ virtual const extrinsic_state *get_ext_state () const = 0;
+
+ /* Hook for clients to access the "malloc" state machine in
+ any underlying program_state. */
+ virtual bool get_malloc_map (sm_state_map **out_smap,
+ const state_machine **out_sm,
+ unsigned *out_sm_idx) = 0;
};
/* A "do nothing" subclass of region_model_context. */
@@ -899,6 +923,18 @@ public:
uncertainty_t *get_uncertainty () OVERRIDE { return NULL; }
void purge_state_involving (const svalue *sval ATTRIBUTE_UNUSED) OVERRIDE {}
+
+ void bifurcate (custom_edge_info *info) OVERRIDE;
+ void terminate_path () OVERRIDE;
+
+ const extrinsic_state *get_ext_state () const OVERRIDE { return NULL; }
+
+ bool get_malloc_map (sm_state_map **,
+ const state_machine **,
+ unsigned *) OVERRIDE
+ {
+ return false;
+ }
};
/* A subclass of region_model_context for determining if operations fail
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 74c6fee..bf5e3c3 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "analyzer/function-set.h"
+#include "analyzer/program-state.h"
#if ENABLE_ANALYZER
@@ -387,6 +388,12 @@ public:
static bool unaffected_by_call_p (tree fndecl);
+ void on_realloc_with_move (region_model *model,
+ sm_state_map *smap,
+ const svalue *old_ptr_sval,
+ const svalue *new_ptr_sval,
+ const extrinsic_state &ext_state) const;
+
standard_deallocator_set m_free;
standard_deallocator_set m_scalar_delete;
standard_deallocator_set m_vector_delete;
@@ -1836,54 +1843,65 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
}
}
-/* Implementation of realloc(3):
-
- void *realloc(void *ptr, size_t size);
-
- realloc(3) is awkward.
+/* Handle a call to "realloc".
+ Check for free of non-heap or mismatching allocators,
+ transitioning to the "stop" state for such cases.
- We currently don't have a way to express multiple possible outcomes
- from a function call, "bifurcating" the state such as:
- - success: non-NULL is returned
- - failure: NULL is returned, existing buffer is not freed.
- or even an N-way state split e.g.:
- - buffer grew successfully in-place
- - buffer was successfully moved to a larger allocation
- - buffer was successfully contracted
- - realloc failed, returning NULL, without freeing existing buffer.
- (PR analyzer/99260 tracks this)
-
- Given that we can currently only express one outcome, eliminate
- false positives by dropping state from the buffer. */
+ Otherwise, region_model::impl_call_realloc will later
+ get called (which will handle other sm-state transitions
+ when the state is bifurcated). */
void
malloc_state_machine::on_realloc_call (sm_context *sm_ctxt,
- const supernode *node ATTRIBUTE_UNUSED,
+ const supernode *node,
const gcall *call) const
{
- tree ptr = gimple_call_arg (call, 0);
+ const unsigned argno = 0;
+ const deallocator *d = &m_realloc;
+
+ tree arg = gimple_call_arg (call, argno);
- state_t state = sm_ctxt->get_state (call, ptr);
+ state_t state = sm_ctxt->get_state (call, arg);
- /* Detect mismatches. */
if (unchecked_p (state) || nonnull_p (state))
{
const allocation_state *astate = as_a_allocation_state (state);
gcc_assert (astate->m_deallocators);
- if (astate->m_deallocators != &m_free)
+ if (!astate->m_deallocators->contains_p (&m_free.m_deallocator))
{
/* Wrong allocator. */
- tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
pending_diagnostic *pd
- = new mismatching_deallocation (*this, diag_ptr,
+ = new mismatching_deallocation (*this, diag_arg,
astate->m_deallocators,
- &m_realloc);
- sm_ctxt->warn (node, call, ptr, pd);
+ d);
+ sm_ctxt->warn (node, call, arg, pd);
+ sm_ctxt->set_next_state (call, arg, m_stop);
+ if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+ path_ctxt->terminate_path ();
}
}
-
- /* Transition ptr to "stop" state. */
- sm_ctxt->set_next_state (call, ptr, m_stop);
+ else if (state == m_free.m_deallocator.m_freed)
+ {
+ /* freed -> stop, with warning. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ sm_ctxt->warn (node, call, arg,
+ new double_free (*this, diag_arg, "free"));
+ sm_ctxt->set_next_state (call, arg, m_stop);
+ if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+ path_ctxt->terminate_path ();
+ }
+ else if (state == m_non_heap)
+ {
+ /* non-heap -> stop, with warning. */
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
+ sm_ctxt->warn (node, call, arg,
+ new free_of_non_heap (*this, diag_arg,
+ d->m_name));
+ sm_ctxt->set_next_state (call, arg, m_stop);
+ if (path_context *path_ctxt = sm_ctxt->get_path_context ())
+ path_ctxt->terminate_path ();
+ }
}
/* Implementation of state_machine::on_phi vfunc for malloc_state_machine. */
@@ -2015,6 +2033,30 @@ malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt,
sm_ctxt->set_next_state (stmt, lhs, m_null);
}
+/* Special-case hook for handling realloc, for the "success with move to
+ a new buffer" case, marking OLD_PTR_SVAL as freed and NEW_PTR_SVAL as
+ non-null.
+
+ This is similar to on_deallocator_call and on_allocator_call,
+ but the checks happen in on_realloc_call, and by splitting the states. */
+
+void
+malloc_state_machine::
+on_realloc_with_move (region_model *model,
+ sm_state_map *smap,
+ const svalue *old_ptr_sval,
+ const svalue *new_ptr_sval,
+ const extrinsic_state &ext_state) const
+{
+ smap->set_state (model, old_ptr_sval,
+ m_free.m_deallocator.m_freed,
+ NULL, ext_state);
+
+ smap->set_state (model, new_ptr_sval,
+ m_free.m_nonnull,
+ NULL, ext_state);
+}
+
} // anonymous namespace
/* Internal interface to this file. */
@@ -2025,6 +2067,40 @@ make_malloc_state_machine (logger *logger)
return new malloc_state_machine (logger);
}
+/* Specialcase hook for handling realloc, for use by
+ region_model::impl_call_realloc::success_with_move::update_model. */
+
+void
+region_model::on_realloc_with_move (const call_details &cd,
+ const svalue *old_ptr_sval,
+ const svalue *new_ptr_sval)
+{
+ region_model_context *ctxt = cd.get_ctxt ();
+ if (!ctxt)
+ return;
+ const extrinsic_state *ext_state = ctxt->get_ext_state ();
+ if (!ext_state)
+ return;
+
+ sm_state_map *smap;
+ const state_machine *sm;
+ unsigned sm_idx;
+ if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx))
+ return;
+
+ gcc_assert (smap);
+ gcc_assert (sm);
+
+ const malloc_state_machine &malloc_sm
+ = (const malloc_state_machine &)*sm;
+
+ malloc_sm.on_realloc_with_move (this,
+ smap,
+ old_ptr_sval,
+ new_ptr_sval,
+ *ext_state);
+}
+
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index fcbf322..e8cbe2d 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -206,10 +206,10 @@ update_model_for_signal_handler (region_model *model,
/* Custom exploded_edge info: entry into a signal-handler. */
-class signal_delivery_edge_info_t : public exploded_edge::custom_info_t
+class signal_delivery_edge_info_t : public custom_edge_info
{
public:
- void print (pretty_printer *pp) FINAL OVERRIDE
+ void print (pretty_printer *pp) const FINAL OVERRIDE
{
pp_string (pp, "signal delivered");
}
@@ -220,15 +220,18 @@ public:
return custom_obj;
}
- void update_model (region_model *model,
- const exploded_edge &eedge) FINAL OVERRIDE
+ bool update_model (region_model *model,
+ const exploded_edge *eedge,
+ region_model_context *) const FINAL OVERRIDE
{
- update_model_for_signal_handler (model, eedge.m_dest->get_function ());
+ gcc_assert (eedge);
+ update_model_for_signal_handler (model, eedge->m_dest->get_function ());
+ return true;
}
void add_events_to_path (checker_path *emission_path,
const exploded_edge &eedge ATTRIBUTE_UNUSED)
- FINAL OVERRIDE
+ const FINAL OVERRIDE
{
emission_path->add_event
(new precanned_custom_event
diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h
index 6bb036e..02faffb 100644
--- a/gcc/analyzer/sm.h
+++ b/gcc/analyzer/sm.h
@@ -257,6 +257,11 @@ public:
Otherwise return NULL_TREE. */
virtual tree is_zero_assignment (const gimple *stmt) = 0;
+ virtual path_context *get_path_context () const
+ {
+ return NULL;
+ }
+
protected:
sm_context (int sm_idx, const state_machine &sm)
: m_sm_idx (sm_idx), m_sm (sm) {}
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index 6913161..5f2fe4c 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -105,7 +105,8 @@ svalue::to_json () const
tree
svalue::maybe_get_constant () const
{
- if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ())
+ const svalue *sval = unwrap_any_unmergeable ();
+ if (const constant_svalue *cst_sval = sval->dyn_cast_constant_svalue ())
return cst_sval->get_constant ();
else
return NULL_TREE;
diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
index 9f92bcf..2db1b3f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c
@@ -8,7 +8,8 @@ void *
test_realloc_1 (void *p, size_t new_sz)
{
void *q = realloc (p, new_sz);
- __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+ __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */
+ /* { dg-warning "capacity: 'INIT_VAL\\(new_sz\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */
return q;
}
@@ -18,8 +19,9 @@ test_realloc_2 (size_t sz_a, size_t sz_b)
void *p = malloc (sz_a);
__analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_a_\[^\n\r\]*\\)'" } */
void *q = realloc (p, sz_b);
- __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
- return p;
+ __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */
+ /* { dg-warning "capacity: 'INIT_VAL\\(sz_b\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */
+ return q; /* { dg-warning "leak of 'p'" } */
}
void *
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
index c6179e9..459357c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
/* Verify absence of false positive from -Wanalyzer-mismatching-deallocation
on realloc(3).
Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/command.c#L115
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
index 3e7ffd6..d64b045 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+
/* Verify absence of false positive from -Wanalyzer-mismatching-deallocation
on realloc(3).
Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/debug.c#L115
diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
index a6c6bfc..606a19a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-Wno-free-nonheap-object" } */
+
typedef __SIZE_TYPE__ size_t;
#define NULL ((void *)0)
@@ -20,11 +22,10 @@ void *test_1 (void *ptr)
void *test_2 (void *ptr)
{
- void *p = malloc (1024);
- p = realloc (p, 4096);
- /* TODO: should warn about the leak when the above call fails (PR analyzer/99260). */
+ void *p = malloc (1024); /* { dg-message "allocated here" } */
+ p = realloc (p, 4096); /* { dg-message "when 'realloc' fails" } */
free (p);
-}
+} /* { dg-warning "leak of 'p'" } */ // ideally this would be on the realloc stmt
void *test_3 (void *ptr)
{
@@ -44,8 +45,8 @@ void *test_4 (void)
int *test_5 (int *p)
{
*p = 42;
- int *q = realloc (p, sizeof(int) * 4);
- *q = 43; /* { dg-warning "possibly-NULL 'q'" "PR analyzer/99260" { xfail *-*-* } } */
+ int *q = realloc (p, sizeof(int) * 4); /* { dg-message "when 'realloc' fails" } */
+ *q = 43; /* { dg-warning "dereference of NULL 'q'" } */
return q;
}
@@ -53,3 +54,37 @@ void test_6 (size_t sz)
{
void *p = realloc (NULL, sz);
} /* { dg-warning "leak of 'p'" } */
+
+/* The analyzer should complain about realloc of non-heap. */
+
+void *test_7 (size_t sz)
+{
+ char buf[100];
+ void *p = realloc (&buf, sz); /* { dg-warning "'realloc' of '&buf' which points to memory not on the heap" } */
+ return p;
+}
+
+/* Mismatched allocator. */
+
+struct foo
+{
+ int m_int;
+};
+
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+ __attribute__ ((malloc (foo_release)));
+
+void test_8 (void)
+{
+ struct foo *p = foo_acquire ();
+ void *q = realloc (p, 1024); /* { dg-warning "'p' should have been deallocated with 'foo_release' but was deallocated with 'realloc'" } */
+}
+
+/* We should complain about realloc on a freed pointer. */
+
+void test_9 (void *p)
+{
+ free (p);
+ void *q = realloc (p, 1024); /* { dg-warning "double-'free' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-2.c b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c
new file mode 100644
index 0000000..a397753
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c
@@ -0,0 +1,80 @@
+#include "analyzer-decls.h"
+
+typedef __SIZE_TYPE__ size_t;
+
+#define NULL ((void *)0)
+
+extern void *malloc (size_t __size)
+ __attribute__ ((__nothrow__ , __leaf__))
+ __attribute__ ((__malloc__))
+ __attribute__ ((__alloc_size__ (1)));
+extern void *realloc (void *__ptr, size_t __size)
+ __attribute__ ((__nothrow__ , __leaf__))
+ __attribute__ ((__warn_unused_result__))
+ __attribute__ ((__alloc_size__ (2)));
+extern void free (void *__ptr)
+ __attribute__ ((__nothrow__ , __leaf__));
+
+char *test_8 (size_t sz)
+{
+ char *p, *q;
+
+ p = malloc (3);
+ if (!p)
+ return NULL;
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */
+
+ p[0] = 'a';
+ p[1] = 'b';
+ p[2] = 'c';
+
+ __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+
+ q = realloc (p, 6);
+
+ /* We should have 3 nodes, corresponding to "failure",
+ "success without moving", and "success with moving". */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
+
+ if (q)
+ {
+ __analyzer_dump_capacity (q); /* { dg-warning "capacity: '\\(size_t\\)6'" } */
+ q[3] = 'd';
+ q[4] = 'e';
+ q[5] = 'f';
+ if (q == p)
+ {
+ /* "realloc" success, growing the buffer in-place. */
+ __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+ // TODO
+ }
+ else
+ {
+ /* "realloc" success, moving the buffer (and thus freeing "p"). */
+ __analyzer_eval (q[0] == 'a'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (q[1] == 'b'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (q[2] == 'c'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[0] == 'a'); /* { dg-warning "UNKNOWN" "unknown" } */
+ /* { dg-warning "use after 'free' of 'p'" "use after free" { target *-*-* } .-1 } */
+ }
+ __analyzer_eval (q[3] == 'd'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (q[4] == 'e'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (q[5] == 'f'); /* { dg-warning "TRUE" } */
+ }
+ else
+ {
+ /* "realloc" failure. p should be unchanged. */
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */
+ __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */
+ return p;
+ }
+
+ return q;
+}