public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
From: David Malcolm <dmalcolm@redhat.com>
To: Tim Lange <mail@tim-lange.me>
Cc: gcc@gcc.gnu.org
Subject: Re: [RFC] analyzer: allocation size warning
Date: Wed, 22 Jun 2022 14:23:08 -0400	[thread overview]
Message-ID: <15b109860a61776e87f83b0286558fcc9b7c4047.camel@redhat.com> (raw)
In-Reply-To: <20220622145755.38734-1-mail@tim-lange.me>

On Wed, 2022-06-22 at 16:57 +0200, Tim Lange wrote:
> The checker reaches region-model.cc#3083 in my patch with the
>   impl_region_model_context
> on the 'after' node of create_buffer() but then discards the warning
> inside
> impl_region_model_context::warn because m_stmt is null. Even if m_stmt
> were
> not be NULL at the 'after' node, my warning would be emitted before the
> return edge was taken and thus be wrongly indented like shown below:
> /path/to/.c:10:16: warning: Allocated buffer size is not a multiple of
> the pointee's size [CWE-131] [-Wanalyzer-allocation-size]
>    10 |     int *buf = create_buffer(42);
>       |                ^~~~~~~~~~~~~~~~~
>   ‘main’: events 1-2
>     |
>     |    9 |   int main (int argc, char **argv) {
>     |      |       ^~~~
>     |      |       |
>     |      |       (1) entry to ‘main’
>     |   10 |     int *buf = create_buffer(42);
>     |      |                ~~~~~~~~~~~~~~~~~
>     |      |                |
>     |      |                (2) calling ‘create_buffer’ from ‘main’
>     |
>     +--> ‘create_buffer’: events 3-4
>            |
>            |    4 |   void *create_buffer(int n)
>            |      |         ^~~~~~~~~~~~~
>            |      |         |
>            |      |         (3) entry to ‘create_buffer’
>            |    5 |   {
>            |    6 |     return malloc(n);
>            |      |            ~~~~~~~~~
>            |      |            |
>            |      |            (4) allocated 42 bytes here
>            |
>          ‘main’: event 5
>            |
>            |   10 |     int *buf = create_buffer(42);
>            |      |                ^~~~~~~~~~~~~~~~~
>            |      |                |
>            |      |                (5) Assigned to ‘int *’ here
>            |
>            
> The correct warning should be:
> /path/to/.c:10:16: warning: Allocated buffer size is not a multiple of
> the pointee's size [CWE-131] [-Wanalyzer-allocation-size]
>    10 |     int *buf = create_buffer(42);
>       |                ^~~~~~~~~~~~~~~~~
>   ‘main’: events 1-2
>     |
>     |    9 |   int main (int argc, char **argv) {
>     |      |       ^~~~
>     |      |       |
>     |      |       (1) entry to ‘main’
>     |   10 |     int *buf = create_buffer(42);
>     |      |                ~~~~~~~~~~~~~~~~~
>     |      |                |
>     |      |                (2) calling ‘create_buffer’ from ‘main’
>     |
>     +------------> ‘create_buffer’: events 3-4
>                    |
>                    |    4 |   void *create_buffer(int n)
>                    |      |         ^~~~~~~~~~~~~
>                    |      |         |
>                    |      |         (3) entry to ‘create_buffer’
>                    |    5 |   {
>                    |    6 |     return malloc(n);
>                    |      |            ~~~~~~~~~
>                    |      |            |
>                    |      |            (4) allocated 42 bytes here
>                    |
> ‘main’: event 5 <--+
>    |
>    |   10 |     int *buf = create_buffer(42);
>    |      |                ^~~~~~~~~~~~~~~~~
>    |      |                |
>    |      |                (5) Assigned to ‘int *’ here
>    |
> For that, the return edge has to be visited to be part of the
> emission_path.
> This is currently not the case as the assignment of the <return_value>
> to
> the caller lhs is handled inside pop_frame, which is transitively
> called
> from program_state::on_edge of the 'after' node of the callee.
> I tried to defer the set_value(caller lhs, <return_value>) call to the
> 'before' node after the return edge but failed to do elegantly. My last
> try
> is in the patch commented out with // FIXME.
> My main problem is that I can not pop the frame and later get the
> return value easily. Deferring the whole pop_frame to the before node
> breaks the assumptions inside exploded_graph::get_or_create_node.
> 
> I don't know what's the best/elegant way of solving this. Is a solution
> to
> attach the return svalue to the return edge and then use it later in
> the
> PK_BEFORE_SUPERNODE?

The ctxt is created here:

#5  0x00000000012f5856 in ana::program_state::on_edge (this=this@entry=0x7fffffffc8c0, eg=..., enode=enode@entry=0x2d8d970, 
    succ=succ@entry=0x2d0e590, uncertainty=uncertainty@entry=0x7fffffffc990) at ../../src/gcc/analyzer/program-state.cc:1035
1035	  if (!m_region_model->maybe_update_for_edge (*succ,
(gdb) list
1030	  impl_region_model_context ctxt (eg, enode,
1031					  &enode->get_state (),
1032					  this,
1033					  uncertainty, NULL,
1034					  last_stmt);
1035	  if (!m_region_model->maybe_update_for_edge (*succ,
1036						      last_stmt,
1037						      &ctxt, NULL))

I tried another approach: to provide a custom stmt_finder for this
ctxt, which uses the "returning call" stmt for the destination
supernode:

diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 7ad581c7fbd..11554de9484 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -996,6 +996,29 @@ program_state::get_current_function () const
   return m_region_model->get_current_function ();
 }
 
+// FIXME
+
+class returning_call_stmt_finder : public stmt_finder
+{
+public:
+  returning_call_stmt_finder (const superedge *succ): m_succ (succ) {}
+
+  stmt_finder *clone () const final override
+  {
+    return new returning_call_stmt_finder (m_succ);
+  }
+  const gimple *find_stmt (const exploded_path &) final override
+  {
+    if (m_succ->m_dest)
+      if (m_succ->m_dest->get_returning_call ())
+       return m_succ->m_dest->get_returning_call ();
+    return NULL;
+  }
+
+private:
+  const superedge *m_succ;
+};
+
 /* Determine if following edge SUCC from ENODE is valid within the graph EG
    and update this state accordingly in-place.
 
@@ -1018,6 +1041,8 @@ program_state::on_edge (exploded_graph &eg,
   const program_point &point = enode->get_point ();
   const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
 
+  returning_call_stmt_finder stmt_finder (succ);
+
   /* For conditionals and switch statements, add the
      relevant conditions (for the specific edge) to new_state;
      skip edges for which the resulting constraints
@@ -1031,7 +1056,7 @@ program_state::on_edge (exploded_graph &eg,
                                  &enode->get_state (),
                                  this,
                                  uncertainty, NULL,
-                                 last_stmt);
+                                 last_stmt, &stmt_finder);
   if (!m_region_model->maybe_update_for_edge (*succ,
                                              last_stmt,
                                              &ctxt, NULL))

Doing so leads to this output:

../../src/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c: In function ‘create_buffer’:
../../src/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c:15:14: warning: Allocated buffer size is not a multiple of the pointee's size [CWE-131] [-Wanalyzer-allocation-size]
   15 |   int *buf = create_buffer(42); /* { dg-warning "" "" { xfail *-*-* } } */
      |              ^~~~~~~~~~~~~~~~~
  ‘test_1’: events 1-2
    |
    |   12 | void test_1(void)
    |      |      ^~~~~~
    |      |      |
    |      |      (1) entry to ‘test_1’
    |......
    |   15 |   int *buf = create_buffer(42); /* { dg-warning "" "" { xfail *-*-* } } */
    |      |              ~~~~~~~~~~~~~~~~~
    |      |              |
    |      |              (2) calling ‘create_buffer’ from ‘test_1’
    |
    +--> ‘create_buffer’: events 3-4
           |
           |    7 | void *create_buffer(int n)
           |      |       ^~~~~~~~~~~~~
           |      |       |
           |      |       (3) entry to ‘create_buffer’
           |    8 | {
           |    9 |   return malloc(n);
           |      |          ~~~~~~~~~
           |      |          |
           |      |          (4) allocated (long unsigned int)42 here
           |
         ‘test_1’: event 5
           |
           |   15 |   int *buf = create_buffer(42); /* { dg-warning "" "" { xfail *-*-* } } */
           |      |              ^~~~~~~~~~~~~~~~~
           |      |              |
           |      |              (5) Assigned to ‘int *’ here
           |

which fixes the stmt and the enclosing function decl for event 5 (the
assignment to the "int *buf"), but annoyingly the stack depth
information is wrong; I think the saved diagnostic is being associated
with the existing exploded_node (in create_buffer), whereas I want it
to use the supernode for test_1, which doesn't yet have an exploded
node when pop_frame is called.  I have various ideas for tackling this:
- have two contexts for pop_frame: one in the old frame, the other in
the new frame (for the caller)
- generalize stmt_finder so it can also update the supernode to use
- rework pop_frame (I've had to do this before, I've run into issues
like this before).

I think it's best to keep this issue as an expected failure, and file a
bug about it, so that we can tackle it by itself, and not block you
from making further progress on this patch.

Various review comments inline below...


Signed-off-by: Tim Lange <mail@tim-lange.me>
---
 gcc/analyzer/analyzer.opt                     |   4 +
 gcc/analyzer/checker-path.cc                  |  12 +-
 gcc/analyzer/checker-path.h                   |   2 +-
 gcc/analyzer/engine.cc                        |  12 +
 gcc/analyzer/pending-diagnostic.h             |  21 ++
 gcc/analyzer/region-model.cc                  | 322 ++++++++++++++++++
 gcc/analyzer/region-model.h                   |   4 +
 .../gcc.dg/analyzer/allocation-size-1.c       |  63 ++++
 .../gcc.dg/analyzer/allocation-size-2.c       |  44 +++
 .../gcc.dg/analyzer/allocation-size-3.c       |  48 +++
 .../gcc.dg/analyzer/allocation-size-4.c       |  92 +++++
 gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c |   2 +
 gcc/testsuite/gcc.dg/analyzer/malloc-4.c      |   2 +-
 gcc/testsuite/gcc.dg/analyzer/pr96639.c       |   2 +
 14 files changed, 627 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 4aea52d3a87..f213989e0bb 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -78,6 +78,10 @@ Wanalyzer-malloc-leak
 Common Var(warn_analyzer_malloc_leak) Init(1) Warning
 Warn about code paths in which a heap-allocated pointer leaks.
 
+Wanalyzer-allocation-size
+Common Var(warn_analyzer_allocation_size) Init(1) Warning
+Warn about code paths in which a buffer is assigned to a incompatible
type.
+


Any time we add a new option to analyzer.opt we're going to need to add
corresponding documentation to gcc/doc/invoke.texi.  Grep for some of
the existing analyzer warnings to see examples.


 Wanalyzer-mismatching-deallocation
 Common Var(warn_analyzer_mismatching_deallocation) Init(1) Warning
 Warn about code paths in which the wrong deallocation function is
called.


[...snip...]

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-
model.cc
index 6b49719d521..acb8bd1bfca 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -73,6 +73,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-ssa-operands.h"
 #include "ssa-iterators.h"
 #include "calls.h"
+#include "print-tree.h"
 
 #if ENABLE_ANALYZER
 
@@ -653,6 +654,66 @@ private:
   tree m_count_cst;
 };
 
+/* Concrete subclass for casts of pointers that lead to trailing
bytes.  */
+
+class dubious_allocation_size
+: public pending_diagnostic_subclass<dubious_allocation_size>
+{
+public:
+  dubious_allocation_size (const region *lhs, const region *rhs, 
+                           const svalue *capacity)
+  : m_lhs(lhs), m_rhs(rhs), m_capacity(capacity) {}
+
+  const char *get_kind () const final override 
+  { 
+    return "dubious_allocation_size"; 
+  }
+
+  bool operator== (const dubious_allocation_size &other) const
+  {
+    return m_lhs == other.m_lhs && m_rhs == other.m_rhs;;
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_allocation_size;
+  }
+
+  bool emit (rich_location *rich_loc) final override
+  {
+    diagnostic_metadata m;
+    m.add_cwe (131);
+    return warning_meta (rich_loc, m, get_controlling_option (),
+              "Allocated buffer size is not a multiple of the
pointee's size");

Style nit: our diagnostic messages don't start with a capital letter.

I think this would benefit from a note, via "inform", saying the
sizeof() the pointee; something like:

  bool warned = warning_meta (rich_loc, m, get_controlling_option (),
                              "allocated buffer size is not a"
                              " multiple of the pointee's size");
  if (warned)
    inform (rich_loc->get_location, "%<sizeof(%E)%> is %qE",
                                    etc, etc);
  return warned;

or somesuch.


+  }
+
+  label_text 
+  describe_region_creation_event (const evdesc::region_creation &ev)
final override
+  {
+    // TODO: better way to print the capacity
+    return ev.formatted_print ("allocated %s here", 

Maybe: "allocated here (%s bytes)" ?

+                                          m_capacity-
>get_desc(true).m_buffer);

Annoyingly, label_text doesn't have an automatically working
destructor, due to us (until recently) only being able to use C++98.  
So as written, this leaks memory.  Now that we can use C++11, maybe we
should fix label_text to have a dtor, move assignment, etc, but it's
probably simpler in the short term to simply fix the leak.

+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev)
final override
+  {
+    return ev.formatted_print ("Assigned to %qT here", m_lhs->get_type
());

Style nit: make initial letter of message lower-case.

+  }
+
+  void mark_interesting_stuff (interesting_t *interest) final override
+  {
+    if (m_lhs)
+      interest->add_region_creation (m_lhs);
+    if (m_rhs)
+      interest->add_region_creation (m_rhs);
+  }
+
+private:
+  const region *m_lhs;
+  const region *m_rhs;
+  const svalue *m_capacity;
+};
+
 /* If ASSIGN is a stmt that can be modelled via
      set_value (lhs_reg, SVALUE, CTXT)
    for some SVALUE, get the SVALUE.
@@ -2799,6 +2860,241 @@ region_model::check_region_for_read (const
region *src_reg,
   check_region_access (src_reg, DIR_READ, ctxt);
 }
 
+/* Returns the trailing bytes on dubious allocation sizes.  */
+
+static unsigned HOST_WIDE_INT 
+capacity_compatible_with_type (tree cst, tree pointee_size_tree)
+{
+  unsigned HOST_WIDE_INT pointee_size = TREE_INT_CST_LOW
(pointee_size_tree);
+  if (pointee_size == 0)
+    return 0;
+  unsigned HOST_WIDE_INT alloc_size = TREE_INT_CST_LOW (cst);
+
+  return alloc_size % pointee_size;
+}
+
+/* Visits svalues and checks whether the 
+   size_cst is a operand of the svalue.  */
+
+class size_visitor : public visitor
+{
+public:
+  size_visitor(tree size_cst, const svalue *sval, constraint_manager
*cm) 
+  : m_size_cst(size_cst), m_sval(sval), m_cm(cm)
+  {
+    sval->accept(this);
+  }
+
+  bool get_result()
+  {
+    /* The result_set gradually builts from atomtic nodes upwards. If
a node is

Typo: atomtic -> atomic

+       in the result_set, itself or one/all of its children have an
operand that
+       is a multiple of the size_cst. If the root is inside, the given
sval 
+       is valid aka a multiple of the size_cst.*/
+    return result_set.contains(m_sval);
+  }
+
+  void 
+  visit_constant_svalue (const constant_svalue *sval) final override
+  {
+    unsigned HOST_WIDE_INT sval_int
+         = TREE_INT_CST_LOW (sval->get_constant ());
+    unsigned HOST_WIDE_INT size_cst_int = TREE_INT_CST_LOW
(m_size_cst);
+    if (size_cst_int == 0 || sval_int % size_cst_int == 0)
+      result_set.add (sval);
+  }
+
+  void 
+  visit_unknown_svalue (const unknown_svalue *sval ATTRIBUTE_UNUSED)
+    final override
+  {
+    result_set.add (sval);
+  }
+
+  void 
+  visit_poisoned_svalue (const poisoned_svalue *sval ATTRIBUTE_UNUSED)
+    final override
+  {
+    result_set.add (sval);
+  }
+  
+  void visit_unaryop_svalue (const unaryop_svalue *sval) 
+  {
+    const svalue *arg = sval->get_arg ();
+    arg->accept (this);
+    if (result_set.contains (arg))
+       result_set.add (sval);
+  }
+
+  void visit_binop_svalue (const binop_svalue *sval) final override
+  {
+    const svalue *arg0 = sval->get_arg0 ();
+    const svalue *arg1 = sval->get_arg1 ();
+
+    arg0->accept (this);
+    arg1->accept (this);
+    if (sval->get_op () == MULT_EXPR)
+      {
+       if (result_set.contains (arg0) || result_set.contains (arg1))
+         result_set.add (sval);
+      }
+    else
+      {
+       if (result_set.contains (arg0) && result_set.contains (arg1))
+         result_set.add (sval);
+      }
+  }
+
+  void visit_repeated_svalue (const repeated_svalue *sval) 
+  {
+    sval->get_inner_svalue ()->accept(this);
+    if (result_set.contains (sval->get_inner_svalue ()))
+      result_set.add (sval);
+  }
+
+  void visit_unmergeable_svalue (const unmergeable_svalue *sval) final
override
+  {
+    sval->get_arg ()->accept (this);
+    if (result_set.contains (sval->get_arg ()))
+      result_set.add (sval);
+  }
+
+  void visit_widening_svalue (const widening_svalue *sval) final
override
+  {
+    const svalue *base = sval->get_base_svalue ();
+    const svalue *iter = sval->get_iter_svalue ();
+
+    base->accept(this);
+    iter->accept(this);
+    if (result_set.contains (base) && result_set.contains (iter))
+      result_set.add (sval);
+  }
+
+  void visit_conjured_svalue (const conjured_svalue *sval
ATTRIBUTE_UNUSED) 
+    final override
+  {
+    if (m_cm->get_equiv_class_by_svalue (sval, NULL))
+      result_set.add (sval);
+  }
+
+  void visit_asm_output_svalue (const asm_output_svalue *sval
ATTRIBUTE_UNUSED) 
+    final override
+  {
+    // TODO: Should we do something else than assume it could be
correct
+    result_set.add (sval);

I think we just have to assume it is.

+  }
+
+  void visit_const_fn_result_svalue (const const_fn_result_svalue 
+                                     *sval ATTRIBUTE_UNUSED) final
override
+  {
+    // TODO: Should we do something else than assume it could be
correct
+    result_set.add (sval);

Probably here as well.

+  }
+
+private:
+  tree m_size_cst;
+  const svalue *m_sval;
+  constraint_manager *m_cm;
+  svalue_set result_set; /* Used as a mapping of svalue*->bool.  */
+};
+
+/* Returns true if there is a constant tree with 
+   the same constant value inside the sval.  */
+
+static bool
+const_operand_in_sval_p (tree type_size_cst, const svalue *sval,
+                         constraint_manager *cm)
+{
+  size_visitor v(type_size_cst, sval, cm);
+  // sval->accept(&v);
+  return v.get_result ();
+}
+
+/* Special handling for structs with "inheritance" or that hold an
unbounded 
+     type. Those will be skipped to prevent false positives.  */
+
+static bool
+struct_or_union_with_inheritance_p (tree maybe_struct)
+{
+  if (RECORD_OR_UNION_TYPE_P (maybe_struct))
+    {
+      tree iter = TYPE_FIELDS (maybe_struct);
+      if (iter == NULL_TREE)
+        return false;
+      if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (iter)))
+        return true;
+
+      tree last_field;
+      while (iter != NULL_TREE)
+        {
+          last_field = iter;
+          iter = DECL_CHAIN (iter);
+        }
+
+      if (last_field != NULL_TREE 
+          && COMPLETE_OR_UNBOUND_ARRAY_TYPE_P (TREE_TYPE
(last_field)))
+        return true;
+    }
+  return false;
+}
+
+void
+region_model::check_region_size (const region *lhs_reg, const svalue
*rhs_sval,
+                                                  region_model_context
*ctxt) const
+{

Please add a comment immediately before this function summarizing its
intent.

+  if (!ctxt)
+    return;
+  
+  const region_svalue *reg_sval = dyn_cast <const region_svalue *>
(rhs_sval);
+  if (!reg_sval)
+    return;
+
+  tree pointer_type = lhs_reg->get_type ();
+  if (pointer_type == NULL_TREE || !POINTER_TYPE_P (pointer_type))
+    return;
+
+  tree pointee_type = TREE_TYPE (pointer_type);
+  /* void * is always compatible and make sure that the pointee_type
actually
+     has a size, or else size_in_bytes might fail.  */
+  if (pointee_type == NULL_TREE || VOID_TYPE_P (pointee_type) 
+      || TYPE_SIZE_UNIT (pointee_type) == NULL_TREE)
+    return;
+  if (struct_or_union_with_inheritance_p (pointee_type))
+    return;
+
+  tree pointee_size_tree = size_in_bytes(pointee_type);
+  /* The size might be unknown e.g. being a array with n elements
+     or casting to char * never has any trailing bytes.  */
+  if (TREE_CODE (pointee_size_tree) != INTEGER_CST
+      || TREE_INT_CST_LOW (pointee_size_tree) == 1)
+    return;
+
+  const svalue *capacity = get_capacity (reg_sval->get_pointee ());
+  switch (capacity->get_kind ())
+    {
+    case svalue_kind::SK_CONSTANT:
+      {
+       const constant_svalue *cap_sval = capacity-
>dyn_cast_constant_svalue ();

You can use:
  as_a <const constant_svalue *> (capacity)
here since we're within the SK_CONSTANT case, avoiding an extra vfunc
call.


+       tree cap = cap_sval->get_constant ();
+       unsigned HOST_WIDE_INT size_diff
+         = capacity_compatible_with_type (cap, pointee_size_tree);
+       if (size_diff != 0)
+         {
+           ctxt->warn (new dubious_allocation_size (lhs_reg, reg_sval-
>get_pointee (), capacity));

Nit: some overlong lines here; please wrap to avoid going over 80
columns.

+         }
+      }
+      break;
+    default:
+      {
+       if (!const_operand_in_sval_p (pointee_size_tree, capacity,
m_constraints))
+         {
+           ctxt->warn (new dubious_allocation_size (lhs_reg, reg_sval-
>get_pointee (), capacity));

Another overlong line.

+         }
+      }
+      break;
+    }
+}
+
 /* Set the value of the region given by LHS_REG to the value given
    by RHS_SVAL.
    Use CTXT to report any warnings associated with writing to
LHS_REG.  */
@@ -2810,6 +3106,8 @@ region_model::set_value (const region *lhs_reg,
const svalue *rhs_sval,
   gcc_assert (lhs_reg);
   gcc_assert (rhs_sval);
 
+  check_region_size(lhs_reg, rhs_sval, ctxt);
+
   check_region_for_write (lhs_reg, ctxt);
 
   m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval,

[...snip...]

diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
new file mode 100644
index 00000000000..cb3df5516e7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
@@ -0,0 +1,63 @@
+#include <stdlib.h>
+
+/* Tests with constant buffer sizes.  */
+
+void test_1 (void)
+{
+  short *ptr = malloc (21 * sizeof(short));
+  free (ptr);
+}
+
+void test_2 (void)
+{
+  int *ptr = malloc (21 * sizeof (short)); /* { dg-line malloc2 } */
+  free (ptr);
+
+  /* { dg-warning "" "" { target *-*-* } malloc2 } */
+  /* { dg-message "" "" { target *-*-* } malloc2 } */
+}
+
+void test_3 (void)
+{
+  void *ptr = malloc (21 * sizeof (short));
+  short *sptr = (short *)ptr;
+  free (sptr);
+}
+
+void test_4 (void)
+{
+  void *ptr = malloc (21 * sizeof (short)); /* { dg-message } */
+  int *iptr = (int *)ptr; /* { dg-line assign } */
+  free (iptr);
+
+  /* { dg-warning "" "" { target *-*-* } assign } */
+  /* { dg-message "" "" { target *-*-* } assign } */
+}
+
+struct s {
+  int i;
+};
+
+void test_5 (void)
+{
+  struct s *ptr = malloc (5 * sizeof (struct s));
+  free (ptr);
+}
+
+void test_6 (void)
+{
+  long *ptr = malloc (5 * sizeof (struct s));  /* { dg-line malloc6 }
*/
+  free (ptr);
+
+  /* { dg-warning "" "" { target *-*-* } malloc6 } */
+  /* { dg-message "" "" { target *-*-* } malloc6 } */
+}
+
+void test_7 (void)
+{
+  char buf[2];
+  int *ptr = (int *)buf; /* { dg-line malloc7 } */
+
+  /* { dg-warning "" "" { target *-*-* } malloc7 } */
+  /* { dg-message "" "" { target *-*-* } malloc7 } */
+}
\ No newline at end of file
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
new file mode 100644
index 00000000000..a619a786a4e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Tests with symbolic buffer sizes.  */
+
+void test_1 (void)
+{
+  int n;
+  scanf("%i", &n);
+  short *ptr = malloc (n * sizeof(short));
+  free (ptr);
+}
+
+void test_2 (void)
+{
+  int n;
+  scanf("%i", &n);
+  int *ptr = malloc (n * sizeof (short)); /* { dg-line malloc } */
+  free (ptr);
+
+  /* { dg-warning "" "" { target *-*-* } malloc } */
+  /* { dg-message "" "" { target *-*-* } malloc } */
+}
+
+void test_3 (void)
+{
+  int n;
+  scanf("%i", &n);
+  void *ptr = malloc (n * sizeof (short));
+  short *sptr = (short *)ptr;
+  free (sptr);
+}
+
+void test_4 (void)
+{
+  int n;
+  scanf("%i", &n);
+  void *ptr = malloc (n * sizeof (short)); /* { dg-message } */
+  int *iptr = (int *)ptr; /* { dg-line assign } */
+  free (iptr);
+
+  /* { dg-warning "" "" { target *-*-* } assign } */
+  /* { dg-message "" "" { target *-*-* } assign } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
new file mode 100644
index 00000000000..dafc0e73c63
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
@@ -0,0 +1,48 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+/* CWE-131 example 5 */
+void test_1(void) 
+{
+  int *id_sequence = (int *) malloc (3); /* { dg-line malloc1 } */
+  if (id_sequence == NULL) exit (1);
+
+  id_sequence[0] = 13579;
+  id_sequence[1] = 24680;
+  id_sequence[2] = 97531;
+
+  free (id_sequence);
+
+  /* { dg-warning "" "" { target *-*-* } malloc1 } */
+  /* { dg-message "" "" { target *-*-* } malloc1 } */
+}
+
+void test_2(void)
+{
+  int *ptr = malloc (10 + sizeof(int)); /* { dg-line malloc2 } */
+  free (ptr);
+
+  /* { dg-warning "" "" { target *-*-* } malloc2 } */
+  /* { dg-message "" "" { target *-*-* } malloc2 } */
+}
+
+void test_3(void)
+{
+  int n;
+  scanf("%i", &n);
+  int *ptr = malloc (n + sizeof (int)); /* { dg-line malloc3 } */
+  free (ptr);
+
+  /* { dg-warning "" "" { target *-*-* } malloc3 } */
+  /* { dg-message "" "" { target *-*-* } malloc3 } */
+}
+
+void test_4(void)
+{
+  int n;
+  scanf("%i", &n);
+  int m;
+  scanf("%i", &m);
+  int *ptr = malloc ((n + m) * sizeof (int));
+  free (ptr);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
new file mode 100644
index 00000000000..32e14bad6ec
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
@@ -0,0 +1,92 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Flow warnings */
+
+void *create_buffer(int n)
+{
+  return malloc(n);
+}
+
+void test_1(void) 
+{
+  // FIXME
+  int *buf = create_buffer(42); /* { dg-warning "" "" { xfail *-*-* }
} */
+  free (buf);
+}
+
+void test_2(void) 
+{
+  void *buf = create_buffer(42); /* { dg-message } */
+  int *ibuf = buf; /* { dg-line assign2 } */
+  free (ibuf);
+
+  /* { dg-warning "" "" { target *-*-* } assign2 } */
+  /* { dg-message "" "" { target *-*-* } assign2 } */
+}
+
+void test_3(void)
+{
+  void *buf = malloc(42); /* { dg-message } */
+  if (buf != NULL) /* { dg-message } */
+    {
+      int *ibuf = buf; /* { dg-line assign3 } */
+      free (ibuf);
+    }
+
+  /* { dg-warning "" "" { target *-*-* } assign3 } */
+  /* { dg-message "" "" { target *-*-* } assign3 } */
+}
+
+void test_4(void)
+{
+  int n;
+  scanf("%i", &n);
+
+  int size;
+  if (n == 0)
+    size = 1;
+  else if (n == 1)
+    size = 10;
+  else
+    size = 20;
+
+  int *buf = malloc(size); // Size should be 'unknown' at this point
+  free (buf);
+}
+
+void test_5(void)
+{
+  int n;
+  scanf("%i", &n);
+
+  int size;
+  if (n == 0)
+    size = 2;
+  else
+    size = 10;
+
+  short *buf = malloc(size); // Size should be widened to 2 and 10,
both fit
+  free (buf);
+}
+
+
+void test_6(void)
+{
+  int n;
+  scanf("%i", &n);
+
+  int size;
+  if (n == 0)
+    size = 1;
+  else
+    size = 10;
+
+  short *buf = malloc(size); /* { dg-line malloc6 } */
+  free (buf);
+  
+
+  /* { dg-warning "" "" { target *-*-* } malloc6 } */
+  /* { dg-message "" "" { target *-*-* } malloc6 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
index bd28107d0d7..8fa6a6eb570 100644
--- a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
@@ -1,7 +1,9 @@
+/* { dg-additional-options -Wno-analyzer-allocation-size } */
 /* Adapted from gcc.dg/Wmismatched-dealloc.c.  */
 
 #define A(...) __attribute__ ((malloc (__VA_ARGS__)))
 
+struct FILE;
 typedef struct FILE   FILE;
 typedef __SIZE_TYPE__ size_t;
 

Hopefully this change isn't needed anymore.

diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
b/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
index 908bb28ee50..f9a73c79403 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
@@ -1,4 +1,4 @@
-/* { dg-additional-options "-Wno-incompatible-pointer-types" } */
+/* { dg-additional-options "-Wno-incompatible-pointer-types -Wno-
analyzer-allocation-size" } */
 
 #include <stdlib.h>

Why is this change needed?  Is this another left-over change from
fixing that stray error?

 
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr96639.c
b/gcc/testsuite/gcc.dg/analyzer/pr96639.c
index 02ca3f084a2..6f365c3cb5d 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr96639.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr96639.c
@@ -1,3 +1,5 @@
+/* { dg-additional-options -Wno-analyzer-allocation-size } */
+
 void *calloc (__SIZE_TYPE__, __SIZE_TYPE__);


I added this testcase in 42c5ae5d7f0ad89b75d93c497fe44b6c66da7e76 to
fix a crash due to a NULL type.

Rather than add -Wno-analyzer-allocation-size, please fix the size
passed to the calloc call.

Thanks; hope this is constructive
Dave


  reply	other threads:[~2022-06-22 18:23 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-17 15:54 Tim Lange
2022-06-17 17:15 ` Prathamesh Kulkarni
2022-06-17 19:23   ` Tim Lange
2022-06-17 21:39     ` David Malcolm
2022-06-17 17:48 ` David Malcolm
2022-06-17 20:23   ` Tim Lange
2022-06-17 22:13     ` David Malcolm
2022-06-21 20:00       ` Tim Lange
2022-06-21 23:16         ` David Malcolm
2022-06-22 14:57           ` Tim Lange
2022-06-22 18:23             ` David Malcolm [this message]
2022-06-17 18:34 ` [RFC] analyzer: add " Tim Lange
2022-06-29 15:39 ` [PATCH v2] analyzer: add allocation size checker Tim Lange
2022-06-29 17:39   ` David Malcolm
2022-06-30 20:40     ` Tim Lange
2022-06-30 22:11 ` [PATCH v3] analyzer: add allocation size checker [PR105900] Tim Lange
2022-06-30 22:47   ` David Malcolm

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=15b109860a61776e87f83b0286558fcc9b7c4047.camel@redhat.com \
    --to=dmalcolm@redhat.com \
    --cc=gcc@gcc.gnu.org \
    --cc=mail@tim-lange.me \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).