to stderr. */
state->dump (eg.get_ext_state (), true);
}
+ else if (is_special_named_call_p (call, "__analyzer_dump_capacity", 1))
+ state->m_region_model->impl_call_analyzer_dump_capacity (call, &ctxt);
else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
{
/* Handle the builtin "__analyzer_dump_path" by queuing a
if (terminate_path)
return on_stmt_flags::terminate_path ();
- bool any_sm_changes = false;
int sm_idx;
sm_state_map *smap;
FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
/* Allow the state_machine to handle the stmt. */
if (sm.on_stmt (&sm_ctxt, snode, stmt))
unknown_side_effects = false;
- if (*old_smap != *new_smap)
- any_sm_changes = true;
}
if (const gcall *call = dyn_cast <const gcall *> (stmt))
state->m_region_model->on_call_post (call, unknown_side_effects, &ctxt);
- return on_stmt_flags (any_sm_changes);
+ return on_stmt_flags ();
}
/* Consider the effect of following superedge SUCC from this node.
return false;
}
+/* Return true if OLD_STATE and NEW_STATE are sufficiently different that
+ we should split enodes and create an exploded_edge separating them
+ (which makes it easier to identify state changes of intereset when
+ constructing checker_paths). */
+
+static bool
+state_change_requires_new_enode_p (const program_state &old_state,
+ const program_state &new_state)
+{
+ /* Changes in dynamic extents signify creations of heap/alloca regions
+ and resizings of heap regions; likely to be of interest in
+ diagnostic paths. */
+ if (old_state.m_region_model->get_dynamic_extents ()
+ != new_state.m_region_model->get_dynamic_extents ())
+ return true;
+
+ /* Changes in sm-state are of interest. */
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+ {
+ const sm_state_map *old_smap = old_state.m_checker_states[sm_idx];
+ const sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
+ if (*old_smap != *new_smap)
+ return true;
+ }
+
+ return false;
+}
+
/* The core of exploded_graph::process_worklist (the main analysis loop),
handling one node in the worklist.
next_state = next_state.prune_for_point (*this, next_point, node,
&uncertainty);
- if (flags.m_sm_changes || flag_analyzer_fine_grained)
+ if (flag_analyzer_fine_grained
+ || state_change_requires_new_enode_p (old_state, next_state))
{
program_point split_point
= program_point::before_stmt (point.get_supernode (),
/* The result of on_stmt. */
struct on_stmt_flags
{
- on_stmt_flags (bool sm_changes)
- : m_sm_changes (sm_changes),
- m_terminate_path (false)
+ on_stmt_flags () : m_terminate_path (false)
{}
static on_stmt_flags terminate_path ()
{
- return on_stmt_flags (true, true);
+ return on_stmt_flags (true);
}
- static on_stmt_flags state_change (bool any_sm_changes)
- {
- return on_stmt_flags (any_sm_changes, false);
- }
-
- /* Did any sm-changes occur handling the stmt. */
- bool m_sm_changes : 1;
-
/* Should we stop analyzing this path (on_stmt may have already
added nodes/edges, e.g. when handling longjmp). */
bool m_terminate_path : 1;
private:
- on_stmt_flags (bool sm_changes,
- bool terminate_path)
- : m_sm_changes (sm_changes),
- m_terminate_path (terminate_path)
+ on_stmt_flags (bool terminate_path)
+ : m_terminate_path (terminate_path)
{}
};
/* Purge dead svals from constraints. */
dest_state.m_region_model->get_constraints ()->on_liveness_change
(maybe_dest_svalues, dest_state.m_region_model);
+
+ /* Purge dead heap-allocated regions from dynamic extents. */
+ for (const svalue *sval : dead_svals)
+ if (const region_svalue *region_sval = sval->dyn_cast_region_svalue ())
+ {
+ const region *reg = region_sval->get_pointee ();
+ if (reg->get_kind () == RK_HEAP_ALLOCATED)
+ dest_state.m_region_model->unset_dynamic_extents (reg);
+ }
}
#if CHECKING_P
program_state s (ext_state);
region_model *model = s.m_region_model;
const svalue *size_in_bytes
- = mgr->get_or_create_unknown_svalue (integer_type_node);
+ = mgr->get_or_create_unknown_svalue (size_type_node);
const region *new_reg = model->create_region_for_heap_alloc (size_in_bytes);
const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
model->set_value (model->get_lvalue (p, NULL),
region_model *model0 = s0.m_region_model;
const svalue *size_in_bytes
- = mgr->get_or_create_unknown_svalue (integer_type_node);
+ = mgr->get_or_create_unknown_svalue (size_type_node);
const region *new_reg = model0->create_region_for_heap_alloc (size_in_bytes);
const svalue *ptr_sval = mgr->get_ptr_svalue (ptr_type_node, new_reg);
model0->set_value (model0->get_lvalue (p, &ctxt),
warning_at (call->location, 0, "svalue: %qs", desc.m_buffer);
}
+/* Handle a call to "__analyzer_dump_capacity".
+
+ Emit a warning describing the capacity of the base region of
+ the region pointed to by the 1st argument.
+ This is for use when debugging, and may be of use in DejaGnu tests. */
+
+void
+region_model::impl_call_analyzer_dump_capacity (const gcall *call,
+ region_model_context *ctxt)
+{
+ tree t_ptr = gimple_call_arg (call, 0);
+ const svalue *sval_ptr = get_rvalue (t_ptr, ctxt);
+ const region *reg = deref_rvalue (sval_ptr, t_ptr, ctxt);
+ const region *base_reg = reg->get_base_region ();
+ const svalue *capacity = get_capacity (base_reg);
+ label_text desc = capacity->get_desc (true);
+ warning_at (call->location, 0, "capacity: %qs", desc.m_buffer);
+}
+
/* Handle a call to "__analyzer_eval" by evaluating the input
and dumping as a dummy warning, so that test cases can use
dg-warning to validate the result (and so unexpected warnings will
poisoning pointers. */
const region *freed_reg = ptr_to_region_sval->get_pointee ();
unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
+ m_dynamic_extents.remove (freed_reg);
}
}
{
return m_mutable_svals.end ();
}
+ hash_set<const region *>::iterator begin_mutable_base_regs ()
+ {
+ return m_mutable_base_regs.begin ();
+ }
+ hash_set<const region *>::iterator end_mutable_base_regs ()
+ {
+ return m_mutable_base_regs.end ();
+ }
void dump_to_pp (pretty_printer *pp) const;
#include "analyzer/analyzer-selftests.h"
#include "stor-layout.h"
#include "attribs.h"
+#include "tree-object-size.h"
#if ENABLE_ANALYZER
/* Ctor for region_model: construct an "empty" model. */
region_model::region_model (region_model_manager *mgr)
-: m_mgr (mgr), m_store (), m_current_frame (NULL)
+: m_mgr (mgr), m_store (), m_current_frame (NULL),
+ m_dynamic_extents ()
{
m_constraints = new constraint_manager (mgr);
}
region_model::region_model (const region_model &other)
: m_mgr (other.m_mgr), m_store (other.m_store),
m_constraints (new constraint_manager (*other.m_constraints)),
- m_current_frame (other.m_current_frame)
+ m_current_frame (other.m_current_frame),
+ m_dynamic_extents (other.m_dynamic_extents)
{
}
m_current_frame = other.m_current_frame;
+ m_dynamic_extents = other.m_dynamic_extents;
+
return *this;
}
if (m_current_frame != other.m_current_frame)
return false;
+ if (m_dynamic_extents != other.m_dynamic_extents)
+ return false;
+
gcc_checking_assert (hash () == other.hash ());
return true;
m_constraints->dump_to_pp (pp, multiline);
if (!multiline)
pp_string (pp, "}");
+
+ /* Dump sizes of dynamic regions, if any are known. */
+ if (!m_dynamic_extents.is_empty ())
+ {
+ pp_string (pp, "dynamic_extents:");
+ m_dynamic_extents.dump_to_pp (pp, simple, multiline);
+ }
}
/* Dump a representation of this model to FILE. */
/* Update bindings for all clusters that have escaped, whether above,
or previously. */
m_store.on_unknown_fncall (call, m_mgr->get_store_manager ());
+
+ /* Purge dynamic extents from any regions that have escaped mutably:
+ realloc could have been called on them. */
+ for (hash_set<const region *>::iterator
+ iter = reachable_regs.begin_mutable_base_regs ();
+ iter != reachable_regs.end_mutable_base_regs ();
+ ++iter)
+ {
+ const region *base_reg = (*iter);
+ unset_dynamic_extents (base_reg);
+ }
}
/* Traverse the regions in this model, determining what regions are
}
}
+/* Get the capacity of REG in bytes. */
+
+const svalue *
+region_model::get_capacity (const region *reg) const
+{
+ switch (reg->get_kind ())
+ {
+ default:
+ break;
+ case RK_DECL:
+ {
+ const decl_region *decl_reg = as_a <const decl_region *> (reg);
+ tree decl = decl_reg->get_decl ();
+ if (TREE_CODE (decl) == SSA_NAME)
+ {
+ tree type = TREE_TYPE (decl);
+ tree size = TYPE_SIZE (type);
+ return get_rvalue (size, NULL);
+ }
+ else
+ {
+ tree size = decl_init_size (decl, false);
+ if (size)
+ return get_rvalue (size, NULL);
+ }
+ }
+ break;
+ }
+
+ if (const svalue *recorded = get_dynamic_extents (reg))
+ return recorded;
+
+ return m_mgr->get_or_create_unknown_svalue (sizetype);
+}
+
/* Set the value of the region given by LHS_REG to the value given
by RHS_SVAL. */
if (ctxt)
ctxt->on_condition (lhs, op, rhs);
+ /* If we have ®ION == NULL, then drop dynamic extents for REGION (for
+ the case where REGION is heap-allocated and thus could be NULL). */
+ if (op == EQ_EXPR && zerop (rhs))
+ if (const region_svalue *region_sval = lhs_sval->dyn_cast_region_svalue ())
+ unset_dynamic_extents (region_sval->get_pointee ());
+
return true;
}
/* Unbind svalues for any regions in REG and below.
Find any pointers to such regions; convert them to
- poisoned values of kind PKIND. */
+ poisoned values of kind PKIND.
+ Also purge any dynamic extents. */
void
region_model::unbind_region_and_descendents (const region *reg,
/* Find any pointers to REG or its descendents; convert to poisoned. */
poison_any_pointers_to_descendents (reg, pkind);
+
+ /* Purge dynamic extents of any base regions in REG and below
+ (e.g. VLAs and alloca stack regions). */
+ for (auto iter : m_dynamic_extents)
+ {
+ const region *iter_reg = iter.first;
+ if (iter_reg->descendent_of_p (reg))
+ unset_dynamic_extents (iter_reg);
+ }
}
/* Implementation of BindingVisitor.
&m))
return false;
+ if (!m_dynamic_extents.can_merge_with_p (other_model.m_dynamic_extents,
+ &out_model->m_dynamic_extents))
+ return false;
+
/* Merge constraints. */
constraint_manager::merge (*m_constraints,
*other_model.m_constraints,
region_model::create_region_for_heap_alloc (const svalue *size_in_bytes)
{
const region *reg = m_mgr->create_region_for_heap_alloc ();
- record_dynamic_extents (reg, size_in_bytes);
+ assert_compat_types (size_in_bytes->get_type (), size_type_node);
+ set_dynamic_extents (reg, size_in_bytes);
return reg;
}
region_model::create_region_for_alloca (const svalue *size_in_bytes)
{
const region *reg = m_mgr->create_region_for_alloca (m_current_frame);
- record_dynamic_extents (reg, size_in_bytes);
+ assert_compat_types (size_in_bytes->get_type (), size_type_node);
+ set_dynamic_extents (reg, size_in_bytes);
return reg;
}
-/* Placeholder hook for recording that the size of REG is SIZE_IN_BYTES.
- Currently does nothing. */
+/* Record that the size of REG is SIZE_IN_BYTES. */
void
-region_model::
-record_dynamic_extents (const region *reg ATTRIBUTE_UNUSED,
- const svalue *size_in_bytes ATTRIBUTE_UNUSED)
+region_model::set_dynamic_extents (const region *reg,
+ const svalue *size_in_bytes)
+{
+ assert_compat_types (size_in_bytes->get_type (), size_type_node);
+ m_dynamic_extents.put (reg, size_in_bytes);
+}
+
+/* Get the recording of REG in bytes, or NULL if no dynamic size was
+ recorded. */
+
+const svalue *
+region_model::get_dynamic_extents (const region *reg) const
{
+ if (const svalue * const *slot = m_dynamic_extents.get (reg))
+ return *slot;
+ return NULL;
+}
+
+/* Unset any recorded dynamic size of REG. */
+
+void
+region_model::unset_dynamic_extents (const region *reg)
+{
+ m_dynamic_extents.remove (reg);
}
/* struct model_merger. */
{
test_region_model_context ctxt;
region_model model0 (&mgr);
- tree size = build_int_cst (integer_type_node, 1024);
+ tree size = build_int_cst (size_type_node, 1024);
const svalue *size_sval = mgr.get_or_create_constant_svalue (size);
const region *new_reg = model0.create_region_for_heap_alloc (size_sval);
const svalue *ptr_sval = mgr.get_ptr_svalue (ptr_type_node, new_reg);
tree null_ptr = build_int_cst (ptr_type_node, 0);
const svalue *size_in_bytes
- = mgr.get_or_create_unknown_svalue (integer_type_node);
+ = mgr.get_or_create_unknown_svalue (size_type_node);
const region *reg = model.create_region_for_heap_alloc (size_in_bytes);
const svalue *sval = mgr.get_ptr_svalue (ptr_type_node, reg);
model.set_value (model.get_lvalue (p, NULL), sval, NULL);
const region *reg = model.create_region_for_heap_alloc (size_sval);
const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
- // TODO: verify dynamic extents
+ ASSERT_EQ (model.get_capacity (reg), size_sval);
}
/* Verify that alloca works. */
ASSERT_EQ (reg->get_parent_region (), frame_reg);
const svalue *ptr = mgr.get_ptr_svalue (int_star, reg);
model.set_value (model.get_lvalue (p, &ctxt), ptr, &ctxt);
- // TODO: verify dynamic extents
+ ASSERT_EQ (model.get_capacity (reg), size_sval);
/* Verify that the pointers to the alloca region are replaced by
poisoned values when the frame is popped. */
m_hash_map.remove (reg);
}
+ bool is_empty () const { return m_hash_map.is_empty (); }
+
void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
void dump (bool simple) const;
a tree of regions, along with their associated values.
The representation is graph-like because values can be pointers to
regions.
- It also stores a constraint_manager, capturing relationships between
- the values. */
+ It also stores:
+ - a constraint_manager, capturing relationships between the values, and
+ - dynamic extents, mapping dynamically-allocated regions to svalues (their
+ capacities). */
class region_model
{
public:
+ typedef region_to_value_map dynamic_extents_t;
+
region_model (region_model_manager *mgr);
region_model (const region_model &other);
~region_model ();
bool impl_call_alloca (const call_details &cd);
void impl_call_analyzer_describe (const gcall *call,
region_model_context *ctxt);
+ void impl_call_analyzer_dump_capacity (const gcall *call,
+ region_model_context *ctxt);
void impl_call_analyzer_eval (const gcall *call,
region_model_context *ctxt);
bool impl_call_builtin_expect (const call_details &cd);
store *get_store () { return &m_store; }
const store *get_store () const { return &m_store; }
+ const dynamic_extents_t &
+ get_dynamic_extents () const
+ {
+ return m_dynamic_extents;
+ }
+ const svalue *get_dynamic_extents (const region *reg) const;
+ void set_dynamic_extents (const region *reg,
+ const svalue *size_in_bytes);
+ void unset_dynamic_extents (const region *reg);
+
region_model_manager *get_manager () const { return m_mgr; }
void unbind_region_and_descendents (const region *reg,
void loop_replay_fixup (const region_model *dst_state);
+ const svalue *get_capacity (const region *reg) const;
+
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;
void on_top_level_param (tree param, region_model_context *ctxt);
- void record_dynamic_extents (const region *reg,
- const svalue *size_in_bytes);
-
bool called_from_main_p () const;
const svalue *get_initial_value_for_global (const region *reg) const;
constraint_manager *m_constraints; // TODO: embed, rather than dynalloc?
const frame_region *m_current_frame;
+
+ /* Map from base region to size in bytes, for tracking the sizes of
+ dynamically-allocated regions.
+ This is part of the region_model rather than the region to allow for
+ memory regions to be resized (e.g. by realloc). */
+ dynamic_extents_t m_dynamic_extents;
};
/* Some region_model activity could lead to warnings (e.g. attempts to use an
will dump the copious information about the analyzer's state each time it
reaches the call in its traversal of the source.
+@smallexample
+extern void __analyzer_dump_capacity (const void *ptr);
+@end smallexample
+
+will emit a warning describing the capacity of the base region of
+the region pointed to by the 1st argument.
+
@smallexample
__analyzer_dump_path ();
@end smallexample
/* Dump copious information about the analyzer’s state when reached. */
extern void __analyzer_dump (void);
+/* Emit a warning describing the size of the base region of (*ptr). */
+extern void __analyzer_dump_capacity (const void *ptr);
+
/* Dump information after analysis on all of the exploded nodes at this
program point.
--- /dev/null
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+typedef unsigned __INT32_TYPE__ u32;
+
+void
+test_1 (void)
+{
+ char buf[16];
+ __analyzer_dump_capacity (buf); /* { dg-warning "capacity: '\\(sizetype\\)16'" } */
+}
+
+void
+test_2 (void)
+{
+ char ch;
+ __analyzer_dump_capacity (&ch); /* { dg-warning "capacity: '\\(sizetype\\)1'" } */
+}
+
+struct s3 { char buf[100]; };
+
+void
+test_3 (void)
+{
+ struct s3 s;
+ __analyzer_dump_capacity (&s); /* { dg-warning "capacity: '\\(sizetype\\)100'" } */
+}
+
+/* Capacity refers to the base region, not any offset within it. */
+
+void
+test_4 (void)
+{
+ char buf[1024];
+ __analyzer_dump_capacity (buf + 100); /* { dg-warning "capacity: '\\(sizetype\\)1024'" } */
+}
+
+void
+test_5 (void *p)
+{
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+}
+
+void
+test_malloc (void)
+{
+ void *p = malloc (1024);
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+ free (p);
+}
+
+void
+test_alloca (size_t sz)
+{
+ void *p = alloca (sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+}
+
+void
+test_vla (size_t sz)
+{
+ char buf[sz];
+ __analyzer_dump_capacity (buf); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+}
+
+static void * __attribute__((noinline))
+called_by_test_interproc_malloc (size_t a)
+{
+ return malloc (a);
+}
+
+void *
+test_interproc_malloc (size_t sz)
+{
+ void *p = called_by_test_interproc_malloc (sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+ return p;
+}
+
+struct s
+{
+ u32 f1;
+ char arr[];
+};
+
+static struct s * __attribute__((noinline))
+alloc_s (size_t num)
+{
+ struct s *p = malloc (sizeof(struct s) + num);
+ return p;
+}
+
+struct s *
+test_trailing_array (void)
+{
+ struct s *p = alloc_s (5);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)9'" } */
+ return p;
+}
+
+void
+test_unknown_arr (int p[])
+{
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+}
--- /dev/null
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+extern void might_realloc (void *);
+extern void cant_realloc (const void *);
+
+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\\)'" } */
+ return q;
+}
+
+void *
+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;
+}
+
+void *
+test_might_realloc (void)
+{
+ void *p = malloc (1024);
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+ might_realloc (p);
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+
+ return p;
+}
+
+void *
+test_cant_realloc (void)
+{
+ void *p = malloc (1024);
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+ cant_realloc (p);
+
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)1024'" } */
+
+ return p;
+}
+
+
--- /dev/null
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+static void __attribute__((noinline))
+__analyzer_callee_1 (size_t inner_sz)
+{
+ void *p = alloca (inner_sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(outer_sz_\[^\n\r\]*\\)'" } */
+}
+
+void
+test_1 (int flag, size_t outer_sz)
+{
+ if (flag)
+ __analyzer_callee_1 (outer_sz);
+
+ /* Verify that we merge state; in particular, the dynamic size of "p"
+ in the called frame should have been purged. */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+void
+test_2 (int flag, size_t sz)
+{
+ if (flag)
+ {
+ void *p = malloc (sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+ free (p);
+ /* The dynamic size of "p" should have been purged. */
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+ }
+
+ /* Verify that we merge state. */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+/* Verify that we purge state on the NULL branch when a malloc result is
+ tested against NULL. */
+
+void
+test_3 (size_t sz)
+{
+ void *p = malloc (sz);
+
+ if (p)
+ {
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+ }
+ else
+ {
+ /* The dynamic size of "p" should have been purged
+ due to "p" being equal to NULL. */
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+ }
+
+ free (p);
+
+ /* The dynamic size of "p" should have been purged. */
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */
+
+ /* Verify that we merge state. */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
+
+/* Verify that we purge dynamic extent of a pointer when it leaks. */
+
+static void __attribute__((noinline))
+__analyzer_callee_4 (size_t inner_sz)
+{
+ void *p = malloc (inner_sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(outer_sz_\[^\n\r\]*\\)'" } */
+} /* { dg-warning "leak of 'p'" } */
+
+void
+test_4 (int flag, size_t outer_sz)
+{
+ if (flag)
+ __analyzer_callee_4 (outer_sz);
+
+ /* Verify that we merge state. */
+ __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}