analyzer: support "bifurcation"; reimplement realloc [PR99260]

Most of the state-management code in the analyzer involves
modifying state objects in-place, which implies a single outcome.
(I originally implemented in-place modification because I wanted
to avoid having to create copies of state objects, and it's now
very difficult to change this aspect of the analyzer's design)

However, there are various special-cases such as "realloc" for which
it's best to split the state into multiple outcomes.

This patch adds a mechanism for "bifurcating" the analysis in places
where there isn't a split in the CFG, and uses it to implement realloc,
in this case treating it as having 3 possible outcomes:
- failure, returning NULL
- success, growing the buffer in-place without moving it
- success, allocating a new buffer, copying the content of the old
  buffer to it, and freeing the old buffer.

gcc/ChangeLog:
	PR analyzer/99260
	* Makefile.in (ANALYZER_OBJS): Add analyzer/call-info.o.

gcc/analyzer/ChangeLog:
	PR analyzer/99260
	* analyzer.h (class custom_edge_info): New class, adapted from
	exploded_edge::custom_info_t.  Make member functions const.
	Make update_model return bool, converting edge param from
	reference to a pointer, and adding a ctxt param.
	(class path_context): New class.
	* call-info.cc: New file.
	* call-info.h: New file.
	* engine.cc: Include "analyzer/call-info.h" and <memory>.
	(impl_region_model_context::impl_region_model_context): Update for
	new m_path_ctxt field.
	(impl_region_model_context::bifurcate): New.
	(impl_region_model_context::terminate_path): New.
	(impl_region_model_context::get_malloc_map): New.
	(impl_sm_context::impl_sm_context): Update for new m_path_ctxt
	field.
	(impl_sm_context::get_fndecl_for_call): Likewise.
	(impl_sm_context::set_next_state): Likewise.
	(impl_sm_context::warn): Likewise.
	(impl_sm_context::is_zero_assignment): Likewise.
	(impl_sm_context::get_path_context): New.
	(impl_sm_context::m_path_ctxt): New.
	(impl_region_model_context::on_condition): Update for new
	path_ctxt param.  Handle m_enode_for_diag being NULL.
	(impl_region_model_context::on_phi): Update for new path_ctxt
	param.
	(exploded_node::on_stmt): Add path_ctxt param, updating ctor calls
	to use it as necessary.  Use it to bail out after sm-handling,
	if needed.
	(exploded_node::detect_leaks): Update for new path_ctxt param.
	(dynamic_call_info_t::update_model): Update for conversion of
	exploded_edge::custom_info_t to custom_edge_info.
	(dynamic_call_info_t::add_events_to_path): Likewise.
	(rewind_info_t::update_model): Likewise.
	(rewind_info_t::add_events_to_path): Likewise.
	(exploded_edge::exploded_edge): Likewise.
	(exploded_graph::add_edge): Likewise.
	(exploded_graph::maybe_process_run_of_before_supernode_enodes):
	Update for new path_ctxt param.
	(class impl_path_context): New.
	(exploded_graph::process_node): Update for new path_ctxt param.
	Create an impl_path_context and pass it to exploded_node::on_stmt.
	Use it to terminate iterating stmts if terminate_path is called
	on it.  After processing a run of stmts, query path_ctxt to
	potentially terminate the analysis path, and/or to "bifurcate" the
	analysis into multiple additional paths.
	(feasibility_state::maybe_update_for_edge): Update for new
	update_model ctxt param.
	* exploded-graph.h
	(impl_region_model_context::impl_region_model_context): Add
	path_ctxt param.
	(impl_region_model_context::bifurcate): New.
	(impl_region_model_context::terminate_path): New
	(impl_region_model_context::get_ext_state): New.
	(impl_region_model_context::get_malloc_map): New.
	(impl_region_model_context::m_path_ctxt): New field.
	(exploded_node::on_stmt): Add path_ctxt param.
	(class exploded_edge::custom_info_t): Move to analyzer.h, renaming
	to custom_edge_info, and making the changes as noted in analyzer.h
	above.
	(exploded_edge::exploded_edge): Update for these changes to
	exploded_edge::custom_info_t.
	(exploded_edge::m_custom_info): Likewise.
	(class dynamic_call_info_t): Likewise.
	(class rewind_info_t): Likewise.
	(exploded_graph::add_edge): Likewise.
	* program-state.cc (program_state::on_edge): Update for new
	path_ctxt param.
	(program_state::push_call): Likewise.
	(program_state::returning_call): Likewise.
	(program_state::prune_for_point): Likewise.
	* region-model-impl-calls.cc: Include "analyzer/call-info.h".
	(call_details::get_fndecl_for_call): New.
	(region_model::impl_call_realloc): Reimplement.
	* region-model.cc (region_model::on_call_pre): Move call to
	impl_call_realloc to...
	(region_model::on_call_post): ...here.  Consolidate creation
	of call_details instance.
	(noop_region_model_context::bifurcate): New.
	(noop_region_model_context::terminate_path): New.
	* region-model.h (call_details::get_call_stmt): New.
	(call_details::get_fndecl_for_call): New.
	(region_model::on_realloc_with_move): New.
	(region_model_context::bifurcate): New.
	(region_model_context::terminate_path): New.
	(region_model_context::get_ext_state): New.
	(region_model_context::get_malloc_map): New.
	(noop_region_model_context::bifurcate): New.
	(noop_region_model_context::terminate_path): New.
	(noop_region_model_context::get_ext_state): New.
	(noop_region_model_context::get_malloc_map): New.
	* sm-malloc.cc: Include "analyzer/program-state.h".
	(malloc_state_machine::on_realloc_call): Reimplement.
	(malloc_state_machine::on_realloc_with_move): New.
	(region_model::on_realloc_with_move): New.
	* sm-signal.cc (class signal_delivery_edge_info_t): Update for
	conversion from exploded_edge::custom_info_t to custom_edge_info.
	* sm.h (sm_context::get_path_context): New.
	* svalue.cc (svalue::maybe_get_constant): Call
	unwrap_any_unmergeable.

gcc/testsuite/ChangeLog:
	PR analyzer/99260
	* gcc.dg/analyzer/capacity-2.c: Update for changes to realloc
	analysis.
	* gcc.dg/analyzer/pr99193-1.c: Likewise.
	* gcc.dg/analyzer/pr99193-3.c: Likewise.
	* gcc.dg/analyzer/realloc-1.c: Likewise.  Add test coverage for
	realloc of non-heap pointer, realloc from mismatching allocator,
	and realloc on a freed pointer.
	* gcc.dg/analyzer/realloc-2.c: New test.
This commit is contained in:
David Malcolm 2021-08-30 18:36:31 -04:00
parent 8960a29b18
commit eafa9d9692
19 changed files with 1042 additions and 132 deletions

View File

@ -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 \

View File

@ -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,

162
gcc/analyzer/call-info.cc Normal file
View File

@ -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 */

83
gcc/analyzer/call-info.h Normal file
View File

@ -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 */

View File

@ -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);
}
}

View File

@ -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 &);

View File

@ -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);
}

View File

@ -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". */

View File

@ -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. */

View File

@ -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

View File

@ -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):
/* Handle a call to "realloc".
Check for free of non-heap or mismatching allocators,
transitioning to the "stop" state for such cases.
void *realloc(void *ptr, size_t size);
realloc(3) is awkward.
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;
state_t state = sm_ctxt->get_state (call, ptr);
tree arg = gimple_call_arg (call, argno);
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 */

View File

@ -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

View File

@ -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) {}

View File

@ -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;

View File

@ -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 *

View File

@ -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

View File

@ -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

View File

@ -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'" } */
}

View File

@ -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;
}