public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
From: Tim Lange <mail@tim-lange.me>
To: David Malcolm <dmalcolm@redhat.com>, GCC Mailing List <gcc@gcc.gnu.org>
Subject: [RFC] analyzer: allocation size warning
Date: Fri, 17 Jun 2022 17:54:46 +0200	[thread overview]
Message-ID: <AJPMDR.ESMA23K2VZHN1@tim-lange.me> (raw)

Hi everyone,

tracked in PR105900 [0], I'd like to add support for a new warning on 
dubious allocation sizes. The new checker emits a warning when the 
allocation size is not a multiple of the type's size. With the checker, 
following mistakes are detected:
  int *arr = malloc(3); // forgot to multiply by sizeof
  arr[0] = ...;
  arr[1] = ...;
or
  int *buf = malloc (n + sizeof(int)); // probably should be * instead 
of +
Because it is implemented inside the analyzer, it also emits warnings 
when the buffer is first of type void* and later on casted to something 
else. Though, this also inherits a limitation. The checker can not 
distinguish 2 * sizeof(short) from sizeof(int) because sizeof is 
resolved and constants are folded at the point when the analyzer runs. 
As a mitigation, I plan to implement a check in the frontend that emits 
a warning if sizeof(lhs pointee type) is not part of the malloc 
argument.

I'm looking for a first feedback on the phrasing of the diagnostics as 
well on the preliminary patch [1].

On constant buffer sizes, the warnings looks like this:
warning: Allocated buffer size is not a multiple of the pointee's size 
[CWE-131] [-Wanalyzer-allocation-size]
   22 | int *ptr = malloc (10 + sizeof(int)); /* { dg-line malloc2 } */
      | ^~~~~~~~~~~~~~~~~~~~~~~~~
  ‘test_2’: event 1
    |
    | 22 | int *ptr = malloc (10 + sizeof(int)); /* { dg-line malloc2 } 
*/
    | | ^~~~~~~~~~~~~~~~~~~~~~~~~
    | | |
    | | (1) Casting a 14 byte buffer to ‘int *’ leaves 2 trailing 
bytes; either the allocated size is bogus or the type on the left-hand 
side is wrong
    |

On symbolic buffer sizes:
warning: Allocated buffer size is not a multiple of the pointee's size 
[CWE-131] [-Wanalyzer-allocation-size]
   33 | int *ptr = malloc (n + sizeof(int)); /* { dg-line malloc3 } */
      | ^~~~~~~~~~~~~~~~~~~~~~~~
  ‘test_3’: event 1
    |
    | 33 | int *ptr = malloc (n + sizeof(int)); /* { dg-line malloc3 } 
*/
    | | ^~~~~~~~~~~~~~~~~~~~~~~~
    | | |
    | | (1) Allocation is incompatible with ‘int *’; either the 
allocated size is bogus or the type on the left-hand side is wrong
    |

And this is how a simple flow looks like:
warning: Allocated buffer size is not a multiple of the pointee's size 
[CWE-131] [-Wanalyzer-allocation-size]
   39 | int *iptr = (int *)ptr; /* { dg-line assign } */
      | ^~~~
  ‘test_4’: events 1-2
    |
    | 38 | void *ptr = malloc (n * sizeof (short)); /* { dg-message } */
    | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~
    | | |
    | | (1) allocated here
    | 39 | int *iptr = (int *)ptr; /* { dg-line assign } */
    | | ~~~~
    | | |
    | | (2) ‘ptr’ is incompatible with ‘int *’; either the 
allocated size at (1) is bogus or the type on the left-hand side is 
wrong
    |

There are some things to discuss from my side:
* The tests with the "toy re-implementation of CPython's object 
model"[2] fail due to a extra warning emitted. Because the analyzer 
can't know the calculation actually results in a correct buffer size 
when viewed as a string_obj later on, it emits a warning, e.g. at line 
61 in data-model-5.c. The only mitigation would be to disable the 
warning for structs entirely. Now, the question is to rather have noise 
on these cases or disable the warning for structs entirely?
* I'm unable to emit a warning whenever the cast happens at an 
assignment with a call as the rhs, e.g. test_1 in allocation-size-4.c. 
This is because I'm unable to access a region_svalue for the returned 
value. Even in the new_program_state, the svalue of the lhs is still a 
conjured_svalue. Maybe David can lead me to a place where I can access 
the return value's region_svalue or do I have to adapt the engine?
* attr-malloc-6.c and pr96639.c did both contain structs without an 
implementation. Something in the analyzer must have triggered another 
warning about the usage of those without them having an implementation. 
I changed those structs to have an empty implementation, such that the 
additional warning are gone. I think this shouldn't change the test 
case, so is this change okay?

- Tim

[0] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105900
[1] While all tests except the cpython ones work, I have yet to test it 
on large C projects
[2] FAIL: gcc.dg/analyzer/data-model-5.c (test for excess errors)
    FAIL: gcc.dg/analyzer/data-model-5b.c (test for excess errors)
    FAIL: gcc.dg/analyzer/data-model-5c.c (test for excess errors)
    FAIL: gcc.dg/analyzer/data-model-5d.c (test for excess errors)
    FAIL: gcc.dg/analyzer/first-field-2.c (test for excess errors)

-------

Subject: [PATCH] analyzer: add allocation size warning

This patch adds an allocation size checker to the analyzer.
The checker warns when the tracked buffer size is not a multiple of the 
left-hand side pointee's type. This resolves PR analyzer/105900.

The patch is not yet fully tested.

gcc/analyzer/ChangeLog:

        * analyzer.opt: Add Wanalyzer-allocation-size.
        * sm-malloc.cc (class dubious_allocation_size): New 
pending_diagnostic subclass.
        (capacity_compatible_with_type): New.
        (const_operand_in_sval_p): New.
        (struct_or_union_with_inheritance_p): New.
        (check_capacity): New.
        (malloc_state_machine::on_stmt): Add calls to 
on_pointer_assignment.
        (malloc_state_machine::on_allocator_call): Add node to 
parameters and call to on_pointer_assignment.
        (malloc_state_machine::on_pointer_assignment): New.

gcc/testsuite/ChangeLog:

        * gcc.dg/analyzer/attr-malloc-6.c: Disabled 
Wanalyzer-allocation-size and added default implementation for FILE.
        * gcc.dg/analyzer/capacity-1.c: Added dg directives.
        * gcc.dg/analyzer/malloc-4.c: Disabled 
Wanalyzer-allocation-size.
        * gcc.dg/analyzer/pr96639.c: Disabled Wanalyzer-allocation-size 
and added default implementation for foo and bar.
        * gcc.dg/analyzer/allocation-size-1.c: New test.
        * gcc.dg/analyzer/allocation-size-2.c: New test.
        * gcc.dg/analyzer/allocation-size-3.c: New test.
        * gcc.dg/analyzer/allocation-size-4.c: New test.

Signed-off-by: Tim Lange <mail@tim-lange.me>
---
 gcc/analyzer/analyzer.opt | 4 +
 gcc/analyzer/sm-malloc.cc | 363 +++++++++++++++++-
 .../gcc.dg/analyzer/allocation-size-1.c | 54 +++
 .../gcc.dg/analyzer/allocation-size-2.c | 44 +++
 .../gcc.dg/analyzer/allocation-size-3.c | 48 +++
 .../gcc.dg/analyzer/allocation-size-4.c | 39 ++
 gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c | 2 +
 gcc/testsuite/gcc.dg/analyzer/capacity-1.c | 5 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-4.c | 6 +-
 gcc/testsuite/gcc.dg/analyzer/pr96639.c | 2 +
 10 files changed, 559 insertions(+), 8 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.
+
 Wanalyzer-mismatching-deallocation
 Common Var(warn_analyzer_mismatching_deallocation) Init(1) Warning
 Warn about code paths in which the wrong deallocation function is 
called.
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 3bd40425919..790c9f0e57d 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -46,6 +46,8 @@ along with GCC; see the file COPYING3. If not see
 #include "attribs.h"
 #include "analyzer/function-set.h"
 #include "analyzer/program-state.h"
+#include "print-tree.h"
+#include "gimple-pretty-print.h"

 #if ENABLE_ANALYZER

@@ -428,6 +430,7 @@ private:
   get_or_create_deallocator (tree deallocator_fndecl);

   void on_allocator_call (sm_context *sm_ctxt,
+ const supernode *node,
      const gcall *call,
      const deallocator_set *deallocators,
      bool returns_nonnull = false) const;
@@ -444,6 +447,16 @@ private:
   void on_realloc_call (sm_context *sm_ctxt,
    const supernode *node,
    const gcall *call) const;
+ void on_pointer_assignment(sm_context *sm_ctxt,
+ const supernode *node,
+ const gassign *assign_stmt,
+ tree lhs,
+ tree rhs) const;
+ void on_pointer_assignment(sm_context *sm_ctxt,
+ const supernode *node,
+ const gcall *call,
+ tree lhs,
+ tree rhs) const;
   void on_zero_assignment (sm_context *sm_ctxt,
       const gimple *stmt,
       tree lhs) const;
@@ -1432,6 +1445,117 @@ private:
   const char *m_funcname;
 };

+/* Concrete subclass for casts of pointers that lead to trailing 
bytes. */
+
+class dubious_allocation_size : public malloc_diagnostic
+{
+public:
+ dubious_allocation_size (const malloc_state_machine &sm, tree lhs, 
tree rhs,
+ tree size_tree, unsigned HOST_WIDE_INT size_diff)
+ : malloc_diagnostic(sm, rhs), 
m_type(dubious_allocation_type::CONSTANT_SIZE),
+ m_lhs(lhs), m_size_tree(size_tree), m_size_diff(size_diff)
+ {}
+
+ dubious_allocation_size (const malloc_state_machine &sm, tree lhs, 
tree rhs,
+ tree size_tree)
+ : malloc_diagnostic(sm, rhs), 
m_type(dubious_allocation_type::MISSING_OPERAND),
+ m_lhs(lhs), m_size_tree(size_tree), m_size_diff(0)
+ {}
+
+ const char *get_kind () const final override
+ {
+ return "dubious_allocation_size";
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_allocation_size;
+ }
+
+ bool subclass_equal_p (const pending_diagnostic &base_other) const
+ final override
+ {
+ const dubious_allocation_size &other = (const dubious_allocation_size 
&)base_other;
+ return malloc_diagnostic::subclass_equal_p(other)
+ && m_type == other.m_type
+ && same_tree_p (m_lhs, other.m_lhs)
+ && same_tree_p (m_size_tree, other.m_size_tree)
+ && m_size_diff == other.m_size_diff;
+ }
+
+ 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");
+ }
+
+ label_text describe_state_change (const evdesc::state_change &change)
+ override
+ {
+ if (change.m_old_state == m_sm.get_start_state ()
+ && unchecked_p (change.m_new_state))
+ {
+ m_alloc_event = change.m_event_id;
+ if (m_type == dubious_allocation_type::CONSTANT_SIZE)
+ {
+ // TODO: verify that it's the allocation stmt, not a copy
+ return change.formatted_print ("%E bytes allocated here",
+ m_size_tree);
+ }
+ }
+ return malloc_diagnostic::describe_state_change (change);
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) final 
override
+ {
+ if (m_type == dubious_allocation_type::CONSTANT_SIZE)
+ {
+ if (m_alloc_event.known_p ())
+ return ev.formatted_print (
+ "Casting %qE to %qT leaves %wu trailing bytes; either the"
+ " allocated size is bogus or the type on the left-hand side is"
+ " wrong",
+ m_arg, TREE_TYPE (m_lhs), m_size_diff);
+ else
+ return ev.formatted_print (
+ "Casting a %E byte buffer to %qT leaves %wu trailing bytes; either"
+ " the allocated size is bogus or the type on the left-hand side is"
+ " wrong",
+ m_size_tree, TREE_TYPE (m_lhs), m_size_diff);
+ }
+ else if (m_type == dubious_allocation_type::MISSING_OPERAND)
+ {
+ if (m_alloc_event.known_p ())
+ return ev.formatted_print (
+ "%qE is incompatible with %qT; either the allocated size at %@ is"
+ " bogus or the type on the left-hand side is wrong",
+ m_arg, TREE_TYPE (m_lhs), &m_alloc_event);
+ else
+ return ev.formatted_print (
+ "Allocation is incompatible with %qT; either the allocated size is"
+ " bogus or the type on the left-hand side is wrong",
+ TREE_TYPE (m_lhs));
+ }
+
+ gcc_unreachable ();
+ return label_text ();
+ }
+
+private:
+ enum dubious_allocation_type {
+ CONSTANT_SIZE,
+ MISSING_OPERAND
+ };
+
+ dubious_allocation_type m_type;
+ diagnostic_event_id_t m_alloc_event;
+ tree m_lhs;
+ tree m_size_tree;
+ unsigned HOST_WIDE_INT m_size_diff;
+};
+
 /* struct allocation_state : public state_machine::state. */

 /* Implementation of state_machine::state::dump_to_pp vfunc
@@ -1633,6 +1757,160 @@ known_allocator_p (const_tree fndecl, const 
gcall *call)
   return false;
 }

+/* 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;
+}
+
+/* Returns true if there is a constant tree with
+ the same constant value inside the sval. */
+
+static bool
+const_operand_in_sval_p (const svalue *sval, tree size_cst)
+{
+ auto_vec<const svalue *> non_mult_expr;
+ auto_vec<const svalue *> worklist;
+ worklist.safe_push(sval);
+ while (!worklist.is_empty())
+ {
+ const svalue *curr = worklist.pop ();
+ curr = curr->unwrap_any_unmergeable ();
+
+ switch (curr->get_kind())
+ {
+ default:
+ break;
+ case svalue_kind::SK_CONSTANT:
+ {
+ const constant_svalue *cst_sval = curr->dyn_cast_constant_svalue ();
+ unsigned HOST_WIDE_INT sval_int
+ = TREE_INT_CST_LOW (cst_sval->get_constant ());
+ unsigned HOST_WIDE_INT size_cst_int = TREE_INT_CST_LOW (size_cst);
+ if (sval_int % size_cst_int == 0)
+ return true;
+ }
+ break;
+ case svalue_kind::SK_BINOP:
+ {
+ const binop_svalue *b_sval = curr->dyn_cast_binop_svalue ();
+ if (b_sval->get_op () == MULT_EXPR)
+ {
+ worklist.safe_push (b_sval->get_arg0 ());
+ worklist.safe_push (b_sval->get_arg1 ());
+ }
+ else
+ {
+ non_mult_expr.safe_push (b_sval->get_arg0 ());
+ non_mult_expr.safe_push (b_sval->get_arg1 ());
+ }
+ }
+ break;
+ case svalue_kind::SK_UNARYOP:
+ {
+ const unaryop_svalue *un_sval = curr->dyn_cast_unaryop_svalue ();
+ worklist.safe_push (un_sval->get_arg ());
+ }
+ break;
+ case svalue_kind::SK_UNKNOWN:
+ return true;
+ }
+ }
+
+ /* Each expr should be a multiple of the size.
+ E.g. used to catch n + sizeof(int) errors. */
+ bool reduce = !non_mult_expr.is_empty ();
+ while (!non_mult_expr.is_empty() && reduce)
+ {
+ const svalue *expr_sval = non_mult_expr.pop ();
+ reduce &= const_operand_in_sval_p (expr_sval, size_cst);
+ }
+ return reduce;
+}
+
+/* Returns true iff the type is a struct with another struct inside. */
+
+static bool
+struct_or_union_with_inheritance_p (tree type)
+{
+ if (!RECORD_OR_UNION_TYPE_P (type))
+ return false;
+
+ for (tree f = TYPE_FIELDS (type); f; f = TREE_CHAIN (f))
+ if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (f)))
+ return true;
+
+ return false;
+}
+
+static void
+check_capacity (sm_context *sm_ctxt,
+ const malloc_state_machine &sm,
+ const supernode *node,
+ const gimple *stmt,
+ tree lhs,
+ tree rhs,
+ const svalue *capacity)
+{
+ tree pointer_type = TREE_TYPE (lhs);
+ gcc_assert (TREE_CODE (pointer_type) == POINTER_TYPE);
+
+ tree pointee_type = TREE_TYPE (pointer_type);
+ /* void * is always compatible. */
+ if (TREE_CODE (pointee_type) == VOID_TYPE)
+ 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;
+
+ switch (capacity->get_kind ())
+ {
+ default:
+ break;
+ case svalue_kind::SK_CONSTANT:
+ {
+ const constant_svalue *cst_sval = capacity->dyn_cast_constant_svalue 
();
+ tree cst = cst_sval->get_constant ();
+ unsigned HOST_WIDE_INT size_diff
+ = capacity_compatible_with_type (cst, pointee_size_tree);
+ if (size_diff != 0)
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (rhs);
+ sm_ctxt->warn (node, stmt, diag_arg,
+ new dubious_allocation_size (sm, lhs, diag_arg,
+ cst, size_diff));
+ }
+ }
+ break;
+ case svalue_kind::SK_BINOP:
+ case svalue_kind::SK_UNARYOP:
+ {
+ if (!const_operand_in_sval_p (capacity, pointee_size_tree))
+ {
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (rhs);
+ sm_ctxt->warn (node, stmt, diag_arg,
+ new dubious_allocation_size (sm, lhs, diag_arg,
+ pointee_size_tree));
+ }
+ }
+ break;
+ }
+}
+
 /* Implementation of state_machine::on_stmt vfunc for 
malloc_state_machine. */

 bool
@@ -1645,14 +1923,14 @@ malloc_state_machine::on_stmt (sm_context 
*sm_ctxt,
       {
  if (known_allocator_p (callee_fndecl, call))
    {
- on_allocator_call (sm_ctxt, call, &m_free);
+ on_allocator_call (sm_ctxt, node, call, &m_free);
      return true;
    }

  if (is_named_call_p (callee_fndecl, "operator new", call, 1))
- on_allocator_call (sm_ctxt, call, &m_scalar_delete);
+ on_allocator_call (sm_ctxt, node, call, &m_scalar_delete);
  else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
- on_allocator_call (sm_ctxt, call, &m_vector_delete);
+ on_allocator_call (sm_ctxt, node, call, &m_vector_delete);
  else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
    || is_named_call_p (callee_fndecl, "operator delete", call, 2))
    {
@@ -1707,7 +1985,7 @@ malloc_state_machine::on_stmt (sm_context 
*sm_ctxt,
      tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
      bool returns_nonnull
        = lookup_attribute ("returns_nonnull", attrs);
- on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull);
+ on_allocator_call (sm_ctxt, node, call, deallocators, 
returns_nonnull);
    }

  /* Handle "__attribute__((nonnull))". */
@@ -1763,12 +2041,31 @@ malloc_state_machine::on_stmt (sm_context 
*sm_ctxt,
        = mutable_this->get_or_create_deallocator (callee_fndecl);
      on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno);
    }
+
+ /* Handle returns from function calls. */
+ tree lhs = gimple_call_lhs (call);
+ if (lhs && TREE_CODE (TREE_TYPE (lhs)) == POINTER_TYPE
+ && TREE_CODE (gimple_call_return_type (call)) == POINTER_TYPE)
+ on_pointer_assignment (sm_ctxt, node, call, lhs,
+ gimple_call_fn (call));
       }

   if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
     if (any_pointer_p (lhs))
       on_zero_assignment (sm_ctxt, stmt,lhs);

+ /* Handle pointer assignments/casts for dubious allocation size. */
+ if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
+ {
+ if (gimple_num_ops (stmt) == 2)
+ {
+ tree lhs = gimple_assign_lhs (assign_stmt);
+ tree rhs = gimple_assign_rhs1 (assign_stmt);
+ if (any_pointer_p (lhs) && any_pointer_p (rhs))
+ on_pointer_assignment (sm_ctxt, node, assign_stmt, lhs, rhs);
+ }
+ }
+
   /* Handle dereferences. */
   for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
     {
@@ -1818,6 +2115,7 @@ malloc_state_machine::on_stmt (sm_context 
*sm_ctxt,

 void
 malloc_state_machine::on_allocator_call (sm_context *sm_ctxt,
+ const supernode *node,
       const gcall *call,
       const deallocator_set *deallocators,
       bool returns_nonnull) const
@@ -1830,6 +2128,9 @@ malloc_state_machine::on_allocator_call 
(sm_context *sm_ctxt,
      (returns_nonnull
       ? deallocators->m_nonnull
       : deallocators->m_unchecked));
+
+ if (TREE_CODE (TREE_TYPE (lhs)) == POINTER_TYPE)
+ on_pointer_assignment (sm_ctxt, node, call, lhs, gimple_call_fn 
(call));
     }
   else
     {
@@ -1968,6 +2269,60 @@ malloc_state_machine::on_realloc_call 
(sm_context *sm_ctxt,
     }
 }

+/* Handle assignments between two pointers.
+ Check for dubious allocation sizes.
+*/
+
+void
+malloc_state_machine::on_pointer_assignment (sm_context *sm_ctxt,
+ const supernode *node,
+ const gassign *assign_stmt,
+ tree lhs,
+ tree rhs) const
+{
+ /* Do not warn if lhs and rhs are of the same type to not emit 
duplicate
+ warnings on assignments after the cast. */
+ if (pending_diagnostic::same_tree_p (TREE_TYPE (lhs), TREE_TYPE 
(rhs)))
+ return;
+
+ const program_state *state = sm_ctxt->get_old_program_state ();
+ const svalue *r_value = state->m_region_model->get_rvalue (rhs, NULL);
+ if (const region_svalue *reg = dyn_cast <const region_svalue *> 
(r_value))
+ {
+ const svalue *capacity = state->m_region_model->get_capacity
+ (reg->get_pointee ());
+ check_capacity(sm_ctxt, *this, node, assign_stmt, lhs, rhs, capacity);
+ }
+}
+
+void
+malloc_state_machine::on_pointer_assignment (sm_context *sm_ctxt,
+ const supernode *node,
+ const gcall *call,
+ tree lhs,
+ tree fn_decl) const
+{
+ /* Do not warn if lhs and rhs are of the same type to not emit 
duplicate
+ warnings on assignments after the cast. */
+ if (pending_diagnostic::same_tree_p
+ (TREE_TYPE (lhs), TREE_TYPE (gimple_call_return_type (call))))
+ return;
+
+ const program_state *state = sm_ctxt->get_new_program_state ();
+ const svalue *r_value = state->m_region_model->get_rvalue (lhs, NULL);
+ if (const region_svalue *reg = dyn_cast <const region_svalue *> 
(r_value))
+ {
+ const svalue *capacity = state->m_region_model->get_capacity
+ (reg->get_pointee ());
+ check_capacity (sm_ctxt, *this, node, call, lhs, fn_decl, capacity);
+ }
+ else if (const conjured_svalue *con
+ = dyn_cast <const conjured_svalue *> (r_value))
+ {
+ // FIXME: How to get a region_svalue?
+ }
+}
+
 /* Implementation of state_machine::on_phi vfunc for 
malloc_state_machine. */

 void
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..5403c5f41f1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
@@ -0,0 +1,54 @@
+#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 malloc } */
+ free (ptr);
+
+ /* { dg-warning "Allocated buffer size is not a multiple of the 
pointee's size" "" { target *-*-* } malloc } */
+ /* { dg-message "\\(1\\) Casting a 42 byte buffer to 'int \\*' leaves 
2 trailing bytes" "" { target *-*-* } malloc } */
+}
+
+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 "Allocated buffer size is not a multiple of the 
pointee's size" "" { target *-*-* } assign } */
+ /* { dg-message "\\(2\\) Casting 'ptr' to 'int \\*' leaves 2 trailing 
bytes" "" { 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 } */
+}
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..e66d2793f13
--- /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 "Allocated buffer size is not a multiple of the 
pointee's size" "" { target *-*-* } malloc } */
+ /* { dg-message "\\(1\\) Allocation is incompatible with 'int \\*'" 
"" { 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 "Allocated buffer size is not a multiple of the 
pointee's size" "" { target *-*-* } assign } */
+ /* { dg-message "\\(2\\) 'ptr' is incompatible with 'int \\*'; either 
the allocated size at \\(1\\)" "" { 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..4c2b31d6e0a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
@@ -0,0 +1,39 @@
+#include <stddef.h>
+#include <stdlib.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 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c 
b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
index bd28107d0d7..809ee88cf07 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;

diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-1.c 
b/gcc/testsuite/gcc.dg/analyzer/capacity-1.c
index 2d124833296..94f569e390b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/capacity-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/capacity-1.c
@@ -89,8 +89,11 @@ struct s
 static struct s * __attribute__((noinline))
 alloc_s (size_t num)
 {
- struct s *p = malloc (sizeof(struct s) + num);
+ struct s *p = malloc (sizeof(struct s) + num); /* { dg-line malloc } 
*/
   return p;
+
+ /* { dg-warning "" "" { target *-*-* } malloc } */
+ /* { dg-message "" "" { target *-*-* } malloc } */
 }

 struct s *
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-4.c 
b/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
index 908bb28ee50..0ca94250ba2 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-4.c
@@ -1,9 +1,9 @@
-/* { dg-additional-options "-Wno-incompatible-pointer-types" } */
+/* { dg-additional-options "-Wno-incompatible-pointer-types 
-Wno-analyzer-allocation-size" } */

 #include <stdlib.h>

-struct foo;
-struct bar;
+struct foo {};
+struct bar {};
 void *hv (struct foo **tm)
 {
   void *p = __builtin_malloc (4);
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__);

 int
-- 
2.36.1




             reply	other threads:[~2022-06-17 15:54 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-17 15:54 Tim Lange [this message]
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
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=AJPMDR.ESMA23K2VZHN1@tim-lange.me \
    --to=mail@tim-lange.me \
    --cc=dmalcolm@redhat.com \
    --cc=gcc@gcc.gnu.org \
    /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).