Commit a96f1c38 by David Malcolm

analyzer: handle compound assignments [PR94378]

PR analyzer/94378 reports a false -Wanalyzer-malloc-leak
when returning a struct containing a malloc-ed pointer.

The issue is that the assignment code was not handling
compound copies, only copying top-level values from region to region,
and not copying child values.

This patch introduces a region_model::copy_region function, using
it for assignments and when analyzing function return values.
It recursively copies nested values within structs, unions, and
arrays, fixing the bug.

gcc/analyzer/ChangeLog:
	PR analyzer/94378
	* checker-path.cc: Include "bitmap.h".
	* constraint-manager.cc: Likewise.
	* diagnostic-manager.cc: Likewise.
	* engine.cc: Likewise.
	(exploded_node::detect_leaks): Pass null region_id to pop_frame.
	* program-point.cc: Include "bitmap.h".
	* program-state.cc: Likewise.
	* region-model.cc (id_set<region_id>::id_set): Convert to...
	(region_id_set::region_id_set): ...this.
	(svalue_id_set::svalue_id_set): New ctor.
	(region_model::copy_region): New function.
	(region_model::copy_struct_region): New function.
	(region_model::copy_union_region): New function.
	(region_model::copy_array_region): New function.
	(stack_region::pop_frame): Drop return value.  Add
	"result_dst_rid" param; if it is non-null, use copy_region to copy
	the result to it.  Rather than capture and pass a single "known
	used" return value to be used by purge_unused_values, instead
	gather and pass a set of known used return values.
	(root_region::pop_frame): Drop return value.  Add "result_dst_rid"
	param.
	(region_model::on_assignment): Use copy_region.
	(region_model::on_return): Likewise for the result.
	(region_model::on_longjmp): Pass null for pop_frame's
	result_dst_rid.
	(region_model::update_for_return_superedge): Pass the region for the
	return value of the call, if any, to pop_frame, rather than setting
	the lvalue for the lhs of the result.
	(region_model::pop_frame): Drop return value.  Add
	"result_dst_rid" param.
	(region_model::purge_unused_svalues): Convert third param from an
	svalue_id * to an svalue_id_set *, updating the initial populating
	of the "used" bitmap accordingly.  Don't remap it when done.
	(struct selftest::coord_test): New selftest fixture, extracted from...
	(selftest::test_dump_2): ...here.
	(selftest::test_compound_assignment): New selftest.
	(selftest::test_stack_frames): Pass null to new param of pop_frame.
	(selftest::analyzer_region_model_cc_tests): Call the new selftest.
	* region-model.h (class id_set): Delete template.
	(class region_id_set): Reimplement, using old id_set implementation.
	(class svalue_id_set): Likewise.  Convert from auto_sbitmap to
	auto_bitmap.
	(region::get_active_view): New accessor.
	(stack_region::pop_frame): Drop return value.  Add
	"result_dst_rid" param.
	(root_region::pop_frame): Likewise.
	(region_model::pop_frame): Likewise.
	(region_model::copy_region): New decl.
	(region_model::purge_unused_svalues): Convert third param from an
	svalue_id * to an svalue_id_set *.
	(region_model::copy_struct_region): New decl.
	(region_model::copy_union_region): New decl.
	(region_model::copy_array_region): New decl.

gcc/testsuite/ChangeLog:
	PR analyzer/94378
	* gcc.dg/analyzer/compound-assignment-1.c: New test.
	* gcc.dg/analyzer/compound-assignment-2.c: New test.
	* gcc.dg/analyzer/compound-assignment-3.c: New test.
parent 7546463b
2020-04-01 David Malcolm <dmalcolm@redhat.com>
PR analyzer/94378
* checker-path.cc: Include "bitmap.h".
* constraint-manager.cc: Likewise.
* diagnostic-manager.cc: Likewise.
* engine.cc: Likewise.
(exploded_node::detect_leaks): Pass null region_id to pop_frame.
* program-point.cc: Include "bitmap.h".
* program-state.cc: Likewise.
* region-model.cc (id_set<region_id>::id_set): Convert to...
(region_id_set::region_id_set): ...this.
(svalue_id_set::svalue_id_set): New ctor.
(region_model::copy_region): New function.
(region_model::copy_struct_region): New function.
(region_model::copy_union_region): New function.
(region_model::copy_array_region): New function.
(stack_region::pop_frame): Drop return value. Add
"result_dst_rid" param; if it is non-null, use copy_region to copy
the result to it. Rather than capture and pass a single "known
used" return value to be used by purge_unused_values, instead
gather and pass a set of known used return values.
(root_region::pop_frame): Drop return value. Add "result_dst_rid"
param.
(region_model::on_assignment): Use copy_region.
(region_model::on_return): Likewise for the result.
(region_model::on_longjmp): Pass null for pop_frame's
result_dst_rid.
(region_model::update_for_return_superedge): Pass the region for the
return value of the call, if any, to pop_frame, rather than setting
the lvalue for the lhs of the result.
(region_model::pop_frame): Drop return value. Add
"result_dst_rid" param.
(region_model::purge_unused_svalues): Convert third param from an
svalue_id * to an svalue_id_set *, updating the initial populating
of the "used" bitmap accordingly. Don't remap it when done.
(struct selftest::coord_test): New selftest fixture, extracted from...
(selftest::test_dump_2): ...here.
(selftest::test_compound_assignment): New selftest.
(selftest::test_stack_frames): Pass null to new param of pop_frame.
(selftest::analyzer_region_model_cc_tests): Call the new selftest.
* region-model.h (class id_set): Delete template.
(class region_id_set): Reimplement, using old id_set implementation.
(class svalue_id_set): Likewise. Convert from auto_sbitmap to
auto_bitmap.
(region::get_active_view): New accessor.
(stack_region::pop_frame): Drop return value. Add
"result_dst_rid" param.
(root_region::pop_frame): Likewise.
(region_model::pop_frame): Likewise.
(region_model::copy_region): New decl.
(region_model::purge_unused_svalues): Convert third param from an
svalue_id * to an svalue_id_set *.
(region_model::copy_struct_region): New decl.
(region_model::copy_union_region): New decl.
(region_model::copy_array_region): New decl.
2020-03-27 David Malcolm <dmalcolm@redhat.com> 2020-03-27 David Malcolm <dmalcolm@redhat.com>
* program-state.cc (selftest::test_program_state_dumping): Update * program-state.cc (selftest::test_program_state_dumping): Update
......
...@@ -42,6 +42,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -42,6 +42,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/analyzer-logging.h" #include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h" #include "analyzer/sm.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "ordered-hash-map.h" #include "ordered-hash-map.h"
#include "selftest.h" #include "selftest.h"
......
...@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see
#include "digraph.h" #include "digraph.h"
#include "analyzer/supergraph.h" #include "analyzer/supergraph.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "analyzer/region-model.h" #include "analyzer/region-model.h"
#include "analyzer/constraint-manager.h" #include "analyzer/constraint-manager.h"
......
...@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
#include "fibonacci_heap.h" #include "fibonacci_heap.h"
#include "shortest-paths.h" #include "shortest-paths.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "selftest.h" #include "selftest.h"
#include "ordered-hash-map.h" #include "ordered-hash-map.h"
......
...@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
#include "function.h" #include "function.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "ordered-hash-map.h" #include "ordered-hash-map.h"
#include "selftest.h" #include "selftest.h"
...@@ -1361,7 +1362,8 @@ exploded_node::detect_leaks (exploded_graph &eg) const ...@@ -1361,7 +1362,8 @@ exploded_node::detect_leaks (exploded_graph &eg) const
&old_state, &new_state, &old_state, &new_state,
NULL, NULL,
get_stmt ()); get_stmt ());
new_state.m_region_model->pop_frame (true, &stats, &ctxt); new_state.m_region_model->pop_frame (region_id::null (),
true, &stats, &ctxt);
} }
/* Dump the successors and predecessors of this enode to OUTF. */ /* Dump the successors and predecessors of this enode to OUTF. */
......
...@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/supergraph.h" #include "analyzer/supergraph.h"
#include "analyzer/program-point.h" #include "analyzer/program-point.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "selftest.h" #include "selftest.h"
#include "analyzer/region-model.h" #include "analyzer/region-model.h"
......
...@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/analyzer-logging.h" #include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h" #include "analyzer/sm.h"
#include "sbitmap.h" #include "sbitmap.h"
#include "bitmap.h"
#include "tristate.h" #include "tristate.h"
#include "ordered-hash-map.h" #include "ordered-hash-map.h"
#include "selftest.h" #include "selftest.h"
......
...@@ -370,25 +370,24 @@ one_way_id_map<T>::update (T *id) const ...@@ -370,25 +370,24 @@ one_way_id_map<T>::update (T *id) const
*id = get_dst_for_src (*id); *id = get_dst_for_src (*id);
} }
/* A set of IDs within a region_model (either svalue_id or region_id). */ /* A set of region_ids within a region_model. */
template <typename T> class region_id_set
class id_set
{ {
public: public:
id_set (const region_model *model); region_id_set (const region_model *model);
void add_region (T id) void add_region (region_id rid)
{ {
if (!id.null_p ()) if (!rid.null_p ())
bitmap_set_bit (m_bitmap, id.as_int ()); bitmap_set_bit (m_bitmap, rid.as_int ());
} }
bool region_p (T id) const bool region_p (region_id rid) const
{ {
gcc_assert (!id.null_p ()); gcc_assert (!rid.null_p ());
return bitmap_bit_p (const_cast <auto_sbitmap &> (m_bitmap), return bitmap_bit_p (const_cast <auto_sbitmap &> (m_bitmap),
id.as_int ()); rid.as_int ());
} }
unsigned int num_regions () unsigned int num_regions ()
...@@ -400,7 +399,29 @@ private: ...@@ -400,7 +399,29 @@ private:
auto_sbitmap m_bitmap; auto_sbitmap m_bitmap;
}; };
typedef id_set<region_id> region_id_set; /* A set of svalue_ids within a region_model. */
class svalue_id_set
{
public:
svalue_id_set ();
void add_svalue (svalue_id sid)
{
if (!sid.null_p ())
bitmap_set_bit (m_bitmap, sid.as_int ());
}
bool svalue_p (svalue_id sid) const
{
gcc_assert (!sid.null_p ());
return bitmap_bit_p (const_cast <auto_bitmap &> (m_bitmap),
sid.as_int ());
}
private:
auto_bitmap m_bitmap;
};
/* Various operations delete information from a region_model. /* Various operations delete information from a region_model.
...@@ -890,6 +911,7 @@ public: ...@@ -890,6 +911,7 @@ public:
void add_view (region_id view_rid, region_model *model); void add_view (region_id view_rid, region_model *model);
region_id get_view (tree type, region_model *model) const; region_id get_view (tree type, region_model *model) const;
region_id get_active_view () const { return m_active_view_rid; }
bool is_view_p () const { return m_is_view; } bool is_view_p () const { return m_is_view; }
virtual void validate (const region_model &model) const; virtual void validate (const region_model &model) const;
...@@ -1450,8 +1472,9 @@ public: ...@@ -1450,8 +1472,9 @@ public:
void push_frame (region_id frame_rid); void push_frame (region_id frame_rid);
region_id get_current_frame_id () const; region_id get_current_frame_id () const;
svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats, void pop_frame (region_model *model, region_id result_dst_rid,
region_model_context *ctxt); bool purge, purge_stats *stats,
region_model_context *ctxt);
void remap_region_ids (const region_id_map &map) FINAL OVERRIDE; void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
...@@ -1555,8 +1578,9 @@ public: ...@@ -1555,8 +1578,9 @@ public:
vec<svalue_id> *arg_sids, vec<svalue_id> *arg_sids,
region_model_context *ctxt); region_model_context *ctxt);
region_id get_current_frame_id (const region_model &model) const; region_id get_current_frame_id (const region_model &model) const;
svalue_id pop_frame (region_model *model, bool purge, purge_stats *stats, void pop_frame (region_model *model, region_id result_dst_rid,
region_model_context *ctxt); bool purge, purge_stats *stats,
region_model_context *ctxt);
region_id ensure_stack_region (region_model *model); region_id ensure_stack_region (region_model *model);
region_id get_stack_region_id () const { return m_stack_rid; } region_id get_stack_region_id () const { return m_stack_rid; }
...@@ -1724,8 +1748,9 @@ class region_model ...@@ -1724,8 +1748,9 @@ class region_model
region_model_context *ctxt); region_model_context *ctxt);
region_id get_current_frame_id () const; region_id get_current_frame_id () const;
function * get_current_function () const; function * get_current_function () const;
svalue_id pop_frame (bool purge, purge_stats *stats, void pop_frame (region_id result_dst_rid,
region_model_context *ctxt); bool purge, purge_stats *stats,
region_model_context *ctxt);
int get_stack_depth () const; int get_stack_depth () const;
function *get_function_at_depth (unsigned depth) const; function *get_function_at_depth (unsigned depth) const;
...@@ -1781,6 +1806,9 @@ class region_model ...@@ -1781,6 +1806,9 @@ class region_model
svalue_id set_to_new_unknown_value (region_id dst_rid, tree type, svalue_id set_to_new_unknown_value (region_id dst_rid, tree type,
region_model_context *ctxt); region_model_context *ctxt);
void copy_region (region_id dst_rid, region_id src_rid,
region_model_context *ctxt);
tristate eval_condition (svalue_id lhs, tristate eval_condition (svalue_id lhs,
enum tree_code op, enum tree_code op,
svalue_id rhs) const; svalue_id rhs) const;
...@@ -1804,7 +1832,7 @@ class region_model ...@@ -1804,7 +1832,7 @@ class region_model
void purge_unused_svalues (purge_stats *out, void purge_unused_svalues (purge_stats *out,
region_model_context *ctxt, region_model_context *ctxt,
svalue_id *known_used_sid = NULL); svalue_id_set *known_used_sids = NULL);
void remap_svalue_ids (const svalue_id_map &map); void remap_svalue_ids (const svalue_id_map &map);
void remap_region_ids (const region_id_map &map); void remap_region_ids (const region_id_map &map);
...@@ -1858,6 +1886,13 @@ class region_model ...@@ -1858,6 +1886,13 @@ class region_model
region_id get_lvalue_1 (path_var pv, region_model_context *ctxt); region_id get_lvalue_1 (path_var pv, region_model_context *ctxt);
svalue_id get_rvalue_1 (path_var pv, region_model_context *ctxt); svalue_id get_rvalue_1 (path_var pv, region_model_context *ctxt);
void copy_struct_region (region_id dst_rid, struct_region *dst_reg,
struct_region *src_reg, region_model_context *ctxt);
void copy_union_region (region_id dst_rid, union_region *src_reg,
region_model_context *ctxt);
void copy_array_region (region_id dst_rid, array_region *dst_reg,
array_region *src_reg, region_model_context *ctxt);
region_id make_region_for_unexpected_tree_code (region_model_context *ctxt, region_id make_region_for_unexpected_tree_code (region_model_context *ctxt,
tree t, tree t,
const dump_location_t &loc); const dump_location_t &loc);
......
2020-04-01 David Malcolm <dmalcolm@redhat.com>
PR analyzer/94378
* gcc.dg/analyzer/compound-assignment-1.c: New test.
* gcc.dg/analyzer/compound-assignment-2.c: New test.
* gcc.dg/analyzer/compound-assignment-3.c: New test.
2020-04-01 Jakub Jelinek <jakub@redhat.com> 2020-04-01 Jakub Jelinek <jakub@redhat.com>
PR middle-end/94436 PR middle-end/94436
......
#include <stdlib.h>
struct ptr_wrapper
{
int *ptr;
};
struct ptr_wrapper
test_1 (void)
{
struct ptr_wrapper r;
r.ptr = malloc (sizeof (int));
return r;
}
struct ptr_wrapper
test_2 (void)
{
struct ptr_wrapper r, s;
r.ptr = malloc (sizeof (int));
s = r;
return s;
}
struct nested
{
struct ptr_wrapper w;
};
struct nested
test_3 (void)
{
struct nested n;
n.w.ptr = malloc (sizeof (int));
return n;
}
void test_4 (void)
{
struct ptr_wrapper r;
r.ptr = malloc (sizeof (int)); /* { dg-message "allocated here" } */
} /* { dg-warning "leak of 'r.ptr'" } */
/* { dg-bogus "leak of '<unknown>'" "unknown leak" { xfail *-*-* } .-1 } */
static struct ptr_wrapper __attribute__((noinline))
called_by_test_5a (void)
{
struct ptr_wrapper r;
r.ptr = malloc (sizeof (int));
return r;
}
void test_5a (void)
{
struct ptr_wrapper q = called_by_test_5a ();
} /* { dg-warning "leak of 'q.ptr'" } */
/* TODO: show the allocation point. */
static struct ptr_wrapper __attribute__((noinline))
called_by_test_5b (void)
{
struct ptr_wrapper r;
r.ptr = malloc (sizeof (int));
return r; /* { dg-warning "leak" } */
/* TODO: show the allocation point. */
}
void test_5b (void)
{
called_by_test_5b ();
}
#include <stdlib.h>
struct array_wrapper
{
void *ptrs[2];
};
struct array_wrapper
test_1 (void)
{
struct array_wrapper aw1;
aw1.ptrs[0] = malloc (1024);
aw1.ptrs[1] = malloc (512);
return aw1;
}
struct array_wrapper
test_2 (void)
{
struct array_wrapper aw2;
aw2.ptrs[0] = malloc (1024);
aw2.ptrs[1] = malloc (512);
} /* { dg-warning "leak of 'aw2.ptrs.0.'" "leak of element 0" } */
/* { dg-warning "leak of 'aw2.ptrs.1.'" "leak of element 1" { target *-*-* } .-1 } */
#include <stdlib.h>
struct union_wrapper
{
union
{
int i;
void *ptr;
} u;
};
struct union_wrapper
test_1 (void)
{
struct union_wrapper uw1;
uw1.u.ptr = malloc (1024);
return uw1;
}
struct union_wrapper
test_2 (void)
{
struct union_wrapper uw2;
uw2.u.ptr = malloc (1024);
} /* { dg-warning "leak of '\\(void \\*\\)uw2.u'" } */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment