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:
parent
8960a29b18
commit
eafa9d9692
|
@ -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 \
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 &);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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". */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'" } */
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue