public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
@ 2023-07-04 16:25 priour.be
  2023-07-05 20:59 ` David Malcolm
  0 siblings, 1 reply; 7+ messages in thread
From: priour.be @ 2023-07-04 16:25 UTC (permalink / raw)
  To: gcc-patches; +Cc: benjamin priour, dmalcolm

From: benjamin priour <priour.be@gmail.com>

Script contrib/check_GNU_style.sh complains about there being a space
before a left square bracket ("operator new []").
Though, it is actually within a literal string, and the space  
is required to correctly detect the function.

Succesfully regstrapped on x86_64-linux-gnu against trunk 3c776fdf1a8.
Is it OK for trunk ?

Benjamin.

---

Fixed spurious possibly-NULL warning always tagging along throwing
operator new despite it never returning NULL.
Now, operator new is correctly recognized as possibly returning
NULL if and only if it is non-throwing or exceptions have been disabled.
Different standard signatures of operator new are now properly recognized.

Added support of placement new, so that is now properly recognized,
and a 'heap_allocated' region is no longer created for it.
Placement new size is also checked and a 'Wanalyzer-allocation-size'
is emitted when relevant, as well as always a 'Wanalyzer-out-of-bounds'.

gcc/analyzer/ChangeLog:

	PR analyzer/105948
	* analyzer.h (is_placement_new_p): New declaration.
	* call-details.cc
	(call_details::maybe_get_arg_region): New function.
	Returns the region of the argument at given index if possible.
	* call-details.h: Declaration of above function.
	* kf-lang-cp.cc (is_placement_new_p): Returns true if the
	gcall is recognized as a placement new.
	* region-model.cc (region_model::eval_condition):
	Now recursively call itself if one the operand is wrapped in a cast.
	* sm-malloc.cc (malloc_state_machine::on_stmt): Added
	recognition of placement new.

gcc/testsuite/ChangeLog:

	PR analyzer/105948
	* g++.dg/analyzer/out-of-bounds-placement-new.C: New test.
	* g++.dg/analyzer/placement-new.C: Added tests.
	* g++.dg/analyzer/new-2.C: New test.
	* g++.dg/analyzer/noexcept-new.C: New test.
	* g++.dg/analyzer/placement-new-size.C: New test.

Signed-off-by: benjamin priour <priour.be@gmail.com>
---
 gcc/analyzer/analyzer.h                       |  1 +
 gcc/analyzer/call-details.cc                  | 11 +++
 gcc/analyzer/call-details.h                   |  1 +
 gcc/analyzer/kf-lang-cp.cc                    | 85 +++++++++++++++++--
 gcc/analyzer/region-model.cc                  | 21 +++++
 gcc/analyzer/sm-malloc.cc                     | 17 ++--
 gcc/testsuite/g++.dg/analyzer/new-2.C         | 50 +++++++++++
 gcc/testsuite/g++.dg/analyzer/noexcept-new.C  | 48 +++++++++++
 .../analyzer/out-of-bounds-placement-new.C    |  2 +-
 .../g++.dg/analyzer/placement-new-size.C      | 27 ++++++
 gcc/testsuite/g++.dg/analyzer/placement-new.C | 63 +++++++++++++-
 11 files changed, 312 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/analyzer/new-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/noexcept-new.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/placement-new-size.C

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 579517c23e6..b86e5cac74d 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -391,6 +391,7 @@ extern bool is_std_named_call_p (const_tree fndecl, const char *funcname,
 				 const gcall *call, unsigned int num_args);
 extern bool is_setjmp_call_p (const gcall *call);
 extern bool is_longjmp_call_p (const gcall *call);
+extern bool is_placement_new_p (const gcall *call);
 
 extern const char *get_user_facing_name (const gcall *call);
 
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index 17edaf26276..01f061d774e 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -152,6 +152,17 @@ call_details::get_arg_svalue (unsigned idx) const
   return m_model->get_rvalue (arg, m_ctxt);
 }
 
+/* If argument IDX's svalue at the callsite is a region_svalue,
+   return the region it points to.
+   Otherwise return NULL.  */
+
+const region *
+call_details::maybe_get_arg_region (unsigned idx) const
+{
+  const svalue *sval = get_arg_svalue (idx);
+  return sval->maybe_get_region ();
+}
+
 /* Attempt to get the string literal for argument IDX, or return NULL
    otherwise.
    For use when implementing "__analyzer_*" functions that take
diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h
index 14a206ff5d6..aac2b7d33d8 100644
--- a/gcc/analyzer/call-details.h
+++ b/gcc/analyzer/call-details.h
@@ -55,6 +55,7 @@ public:
   tree get_arg_tree (unsigned idx) const;
   tree get_arg_type (unsigned idx) const;
   const svalue *get_arg_svalue (unsigned idx) const;
+  const region *maybe_get_arg_region (unsigned idx) const;
   const char *get_arg_string_literal (unsigned idx) const;
 
   tree get_fndecl_for_call () const;
diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
index 393b4f25e79..258d92919d7 100644
--- a/gcc/analyzer/kf-lang-cp.cc
+++ b/gcc/analyzer/kf-lang-cp.cc
@@ -35,6 +35,34 @@ along with GCC; see the file COPYING3.  If not see
 
 #if ENABLE_ANALYZER
 
+/* Return TRUE if CALL is non-allocating operator new or operator new[]*/
+
+bool is_placement_new_p (const gcall *call)
+{
+  gcc_assert (call);
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return false;
+
+  if (!is_named_call_p (fndecl, "operator new", call, 2)
+    && !is_named_call_p (fndecl, "operator new []", call, 2))
+    return false;
+  tree arg1 = gimple_call_arg (call, 1);
+
+  if (!POINTER_TYPE_P (TREE_TYPE (arg1)))
+    return false;
+
+  /* Sadly, for non-throwing new, the second argument type
+    is not REFERENCE_TYPE but also POINTER_TYPE
+    so a simple check is out of the way.  */
+  tree identifier = TYPE_IDENTIFIER (TREE_TYPE (TREE_TYPE (arg1)));
+  if (!identifier)
+    return true;
+  const char *name = IDENTIFIER_POINTER (identifier);
+  return 0 != strcmp (name, "nothrow_t");
+}
+
 namespace ana {
 
 /* Implementations of specific functions.  */
@@ -46,7 +74,7 @@ class kf_operator_new : public known_function
 public:
   bool matches_call_types_p (const call_details &cd) const final override
   {
-    return cd.num_args () == 1;
+    return cd.num_args () == 1 || cd.num_args () == 2;
   }
 
   void impl_call_pre (const call_details &cd) const final override
@@ -54,13 +82,60 @@ public:
     region_model *model = cd.get_model ();
     region_model_manager *mgr = cd.get_manager ();
     const svalue *size_sval = cd.get_arg_svalue (0);
-    const region *new_reg
-      = model->get_or_create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
-    if (cd.get_lhs_type ())
+    region_model_context *ctxt = cd.get_ctxt ();
+    const gcall *call = cd.get_call_stmt ();
+
+    /* If the call is an allocating new, then create a heap allocated
+    region.  */
+    if (!is_placement_new_p (call))
+      {
+	const region *new_reg
+	  = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
+	if (cd.get_lhs_type ())
+	  {
+	    const svalue *ptr_sval
+	      = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	    cd.maybe_set_lhs (ptr_sval);
+	  }
+      }
+    /* If the call was actually a placement new, check that accessing
+    the buffer lhs is placed into does not result in out-of-bounds.  */
+    else
       {
+	const region *ptr_reg = cd.maybe_get_arg_region (1);
+	if (ptr_reg && cd.get_lhs_type ())
+	  {
+	    const region *base_reg = ptr_reg->get_base_region ();
+	    const svalue *num_bytes_sval = cd.get_arg_svalue (0);
+	    const region *sized_new_reg = mgr->get_sized_region (base_reg,
+	      cd.get_lhs_type (),
+	      num_bytes_sval);
+	    model->check_region_for_write (sized_new_reg,
+	      nullptr,
+	      ctxt);
 	const svalue *ptr_sval
-	  = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	  = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_new_reg);
 	cd.maybe_set_lhs (ptr_sval);
+	  }
+      }
+  }
+
+  void impl_call_post (const call_details &cd) const final override
+  {
+    region_model *model = cd.get_model ();
+    region_model_manager *mgr = cd.get_manager ();
+    tree callee_fndecl = cd.get_fndecl_for_call ();
+    region_model_context *ctxt = cd.get_ctxt ();
+
+    /* If the call is guaranteed to return nonnull
+      then add a nonnull constraint to the allocated region.  */
+    if (!TREE_NOTHROW (TREE_TYPE (callee_fndecl)) && flag_exceptions)
+      {
+	const svalue *nonnull
+	  = mgr->get_or_create_null_ptr (cd.get_lhs_type ());
+	const svalue *result
+	  = model->get_store_value (cd.get_lhs_region (), ctxt);
+	model->add_constraint (result, NE_EXPR, nonnull, ctxt);
       }
   }
 };
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 187013a37cc..599178e3d86 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -3479,6 +3479,27 @@ region_model::eval_condition (const svalue *lhs,
 	}
     }
 
+  /* Attempt to unwrap cast if there is one, and the types match.  */
+  tree lhs_type = lhs->get_type ();
+  tree rhs_type = rhs->get_type ();
+  if (lhs_type && rhs_type)
+  {
+    const unaryop_svalue *lhs_un_op = dyn_cast <const unaryop_svalue *> (lhs);
+    const unaryop_svalue *rhs_un_op = dyn_cast <const unaryop_svalue *> (rhs);
+    if (lhs_un_op && CONVERT_EXPR_CODE_P (lhs_un_op->get_op ())
+      && rhs_un_op && CONVERT_EXPR_CODE_P (rhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs_un_op->get_arg (), op, rhs_un_op->get_arg ());
+
+    else if (lhs_un_op && CONVERT_EXPR_CODE_P (lhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs_un_op->get_arg (), op, rhs);
+
+    else if (rhs_un_op && CONVERT_EXPR_CODE_P (rhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs, op, rhs_un_op->get_arg ());
+  }
+
   /* Otherwise, try constraints.
      Cast to const to ensure we don't change the constraint_manager as we
      do this (e.g. by creating equivalence classes).  */
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index a8c63eb1ce8..41c313c07dd 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -754,7 +754,7 @@ public:
     override
   {
     if (change.m_old_state == m_sm.get_start_state ()
-	&& unchecked_p (change.m_new_state))
+	&& (unchecked_p (change.m_new_state) || nonnull_p (change.m_new_state)))
       // TODO: verify that it's the allocation stmt, not a copy
       return label_text::borrow ("allocated here");
     if (unchecked_p (change.m_old_state)
@@ -1910,11 +1910,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
 	    return true;
 	  }
 
-	if (is_named_call_p (callee_fndecl, "operator new", call, 1))
-	  on_allocator_call (sm_ctxt, 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);
-	else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
+	if (!is_placement_new_p (call))
+	  {
+  bool returns_nonnull = !TREE_NOTHROW (callee_fndecl) && flag_exceptions;
+  if (is_named_call_p (callee_fndecl, "operator new"))
+    on_allocator_call (sm_ctxt, call, &m_scalar_delete, returns_nonnull);
+  else if (is_named_call_p (callee_fndecl, "operator new []"))
+    on_allocator_call (sm_ctxt, call, &m_vector_delete, returns_nonnull);
+	  }
+
+	if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
 		 || is_named_call_p (callee_fndecl, "operator delete", call, 2))
 	  {
 	    on_deallocator_call (sm_ctxt, node, call,
diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C b/gcc/testsuite/g++.dg/analyzer/new-2.C
new file mode 100644
index 00000000000..4e696040a54
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
@@ -0,0 +1,50 @@
+// { dg-additional-options "-O0" }
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_spurious_null_warning_throwing ()
+{
+  int *x = new int; /* { dg-bogus "dereference of possibly-NULL" } */
+  int *y = new int (); /* { dg-bogus "dereference of possibly-NULL" "non-throwing" } */
+  int *arr = new int[3]; /* { dg-bogus "dereference of possibly-NULL" } */ 
+  A *a = new A (); /* { dg-bogus "dereference of possibly-NULL" "throwing new cannot be null" } */
+
+  int z = *y + 2;
+  z = *x + 4; /* { dg-bogus "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-bogus "dereference of possibly-NULL" } */
+
+  delete a;
+  delete y;
+  delete x;
+  delete[] arr;
+}
+
+void test_default_initialization ()
+{
+    int *y = ::new int;
+    int *x = ::new int (); /* { dg-bogus "dereference of possibly-NULL 'operator new" } */
+
+    int b = *x + 3; /* { dg-bogus "dereference of possibly-NULL" } */
+    /* { dg-bogus "use of uninitialized ‘*x’" "" { target *-*-* } .-1 } */
+    int a = *y + 2; /* { dg-bogus "dereference of possibly-NULL 'y'" } */
+    /* { dg-warning "use of uninitialized value '\\*y'" "no default init" { target *-*-* } .-1 } */
+
+    delete x;
+    delete y;
+}
+
+/* From clang core.uninitialized.NewArraySize
+new[] should not be called with an undefined size argument */
+
+void test_garbage_new_array ()
+{
+  int n;
+  int *arr = ::new int[n]; /* { dg-warning "use of uninitialized value 'n'" } */
+  arr[0] = 7;
+  ::delete[] arr; /* no warnings emitted here either */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/noexcept-new.C b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
new file mode 100644
index 00000000000..7699cd99cff
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
@@ -0,0 +1,48 @@
+/* { dg-additional-options "-O0 -fno-exceptions -fno-analyzer-suppress-followups" } */
+#include <new>
+
+/* Test non-throwing variants of operator new */
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_throwing ()
+{
+  int* x = new int;
+  int* y = new int(); /* { dg-warning "dereference of possibly-NULL" } */
+  int* arr = new int[10];
+  A *a = new A(); /* { dg-warning "dereference of possibly-NULL" } */
+
+  int z = *y + 2;
+  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
+  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
+  a->y = a->x + 3;
+
+  delete a;
+  delete y;
+  delete x;
+  delete[] arr;
+}
+
+void test_nonthrowing ()
+{
+  int* x = new(std::nothrow) int;
+  int* y = new(std::nothrow) int();
+  int* arr = new(std::nothrow) int[10];
+
+  int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
+  /* { dg-warning "use of uninitialized value '\\*y'" "" { target *-*-* } .-1 } */
+  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
+  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
+
+  delete y;
+  delete x;
+  delete[] arr;
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C b/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
index d3076c3cf25..33872e6ddab 100644
--- a/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
+++ b/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
@@ -14,6 +14,6 @@ struct int_and_addr {
 
 int test (int_container ic)
 {
-  int_and_addr *iaddr = new (ic.addr ()) int_and_addr;
+  int_and_addr *iaddr = new (ic.addr ()) int_and_addr; /* { dg-warning "stack-based buffer overflow" } */
   return iaddr->i;
 }
diff --git a/gcc/testsuite/g++.dg/analyzer/placement-new-size.C b/gcc/testsuite/g++.dg/analyzer/placement-new-size.C
new file mode 100644
index 00000000000..810944cd137
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/placement-new-size.C
@@ -0,0 +1,27 @@
+/* { dg-additional-options "-Wno-placement-new -Wno-analyzer-use-of-uninitialized-value" } */
+
+#include <new>
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern int get_buf_size ();
+
+void var_too_short ()
+{
+  short s;
+  long *lp = new (&s) long; /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" "" { target *-*-* } .-1 } */
+}
+
+void static_buffer_too_short ()
+{
+  int n = 16;
+  int buf[n];
+  int *p = new (buf) int[n + 1]; /* { dg-warning "stack-based buffer overflow" } */
+}
+
+void symbolic_buffer_too_short ()
+{
+  int n = get_buf_size ();
+  char buf[n];
+  char *p = new (buf) char[n + 10]; /* { dg-warning "stack-based buffer overflow" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/placement-new.C b/gcc/testsuite/g++.dg/analyzer/placement-new.C
index 89055491a48..c0cf7ec4c48 100644
--- a/gcc/testsuite/g++.dg/analyzer/placement-new.C
+++ b/gcc/testsuite/g++.dg/analyzer/placement-new.C
@@ -14,15 +14,74 @@ void test_2 (void)
 {
   char buf[sizeof(int) * 10];
   int *p = new(buf) int[10];
-}
+} // { dg-prune-output "-Wfree-nonheap-object" }
 
 /* Delete of placement new.  */
 
 void test_3 (void)
 {
   char buf[sizeof(int)]; // { dg-message "region created on stack here" }
-  int *p = new(buf) int (42);
+  int *p = new (buf) int (42);
   delete p; // { dg-warning "memory on the stack" }
 }
 
 // { dg-prune-output "-Wfree-nonheap-object" }
+
+void test_4 (void)
+{
+  int buf[5]; // { dg-message "region created on stack here" }
+  int *p = new (&buf[2]) int (42);
+  delete p; // { dg-warning "memory on the stack" }
+}
+
+
+// { dg-prune-output "-Wfree-nonheap-object" }
+
+void test_write_placement_after_delete (void)
+{
+  short *s = ::new short;
+  short *lp = ::new (s) short;
+  ::delete s;
+  *lp = 12; /* { dg-warning "use after 'delete' of 'lp'" "write placement new after buffer deletion" } */
+}
+
+void test_read_placement_after_delete (void)
+{
+  short *s = ::new short;
+  short *lp = ::new (s) short;
+  ::delete s;
+  short m = *lp; // { dg-warning "use after 'delete' of 'lp'" "read placement new after buffer deletion" }
+}
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_use_placement_after_destruction (void)
+{
+  A a;
+  int *lp = ::new (&a.y) int;
+  *lp = 2; /* { dg-bogus "-Wanalyzer-use-of-uninitialized-value" } */
+  a.~A();
+  int m = *lp; /* { dg-warning "use of uninitialized value '\\*lp'" "use of placement after the underlying buffer was destructed." } */
+}
+
+void test_initialization_through_placement (void)
+{
+  int x;
+  int *p = ::new (&x) int;
+  *p = 10;
+  int z = x + 2; /* { dg-bogus "use of uninitialized value 'x'" "x has been initialized through placement pointer" } */
+}
+
+void test_partial_initialization_through_placement (void)
+{
+  char buf[4];
+  char *p = ::new (&buf[2]) char;
+  *p = 10;
+  char *y = ::new (&buf[0]) char;
+  char z = buf[2] + 2; /* { dg-bogus "use of uninitialized value" } */
+  z = *y + 2; /* { dg-warning "use of uninitialized value '\\*y'" "y has only been partially initialized" } */
+}
\ No newline at end of file
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
  2023-07-04 16:25 [PATCH] analyzer: Add support of placement new and improved operator new [PR105948] priour.be
@ 2023-07-05 20:59 ` David Malcolm
  2023-07-05 22:01   ` Benjamin Priour
  0 siblings, 1 reply; 7+ messages in thread
From: David Malcolm @ 2023-07-05 20:59 UTC (permalink / raw)
  To: priour.be, gcc-patches

On Tue, 2023-07-04 at 18:25 +0200, priour.be@gmail.com wrote:
> From: benjamin priour <priour.be@gmail.com>
> 
> Script contrib/check_GNU_style.sh complains about there being a space
> before a left square bracket ("operator new []").
> Though, it is actually within a literal string, and the space  
> is required to correctly detect the function.
> 
> Succesfully regstrapped on x86_64-linux-gnu against trunk
> 3c776fdf1a8.
> Is it OK for trunk ?

Thanks for the patch.

Overall, looks almost ready, but some nitpicks below,,,

[..snip...]


> diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
> index 393b4f25e79..258d92919d7 100644
> --- a/gcc/analyzer/kf-lang-cp.cc
> +++ b/gcc/analyzer/kf-lang-cp.cc
> @@ -35,6 +35,34 @@ along with GCC; see the file COPYING3.  If not see
>  
>  #if ENABLE_ANALYZER
>  
> +/* Return TRUE if CALL is non-allocating operator new or operator
> new[]*/
> +
> +bool is_placement_new_p (const gcall *call)

Please can you extend the leading comment, giving the expected
signatures of the functions, and a link to cppreference.org.

In particular, there's some special-casing here of "nothrow_t" which
would make more sense with a comment up here.

> +{
> +  gcc_assert (call);
> +
> +  tree fndecl = gimple_call_fndecl (call);
> +  if (!fndecl)
> +    return false;
> +
> +  if (!is_named_call_p (fndecl, "operator new", call, 2)
> +    && !is_named_call_p (fndecl, "operator new []", call, 2))
> +    return false;
> +  tree arg1 = gimple_call_arg (call, 1);
> +
> +  if (!POINTER_TYPE_P (TREE_TYPE (arg1)))
> +    return false;
> +
> +  /* Sadly, for non-throwing new, the second argument type
> +    is not REFERENCE_TYPE but also POINTER_TYPE
> +    so a simple check is out of the way.  */
> +  tree identifier = TYPE_IDENTIFIER (TREE_TYPE (TREE_TYPE (arg1)));
> +  if (!identifier)
> +    return true;
> +  const char *name = IDENTIFIER_POINTER (identifier);
> +  return 0 != strcmp (name, "nothrow_t");
> +}
> +
>  namespace ana {
>  
>  /* Implementations of specific functions.  */
> @@ -46,7 +74,7 @@ class kf_operator_new : public known_function
>  public:
>    bool matches_call_types_p (const call_details &cd) const final
> override
>    {
> -    return cd.num_args () == 1;
> +    return cd.num_args () == 1 || cd.num_args () == 2;

Looks like we should also check that arg 0 is of integral type, and
that arg 1 is of pointer type.


>    }
>  
>    void impl_call_pre (const call_details &cd) const final override
> @@ -54,13 +82,60 @@ public:
>      region_model *model = cd.get_model ();
>      region_model_manager *mgr = cd.get_manager ();
>      const svalue *size_sval = cd.get_arg_svalue (0);
> -    const region *new_reg
> -      = model->get_or_create_region_for_heap_alloc (size_sval,
> cd.get_ctxt ());
> -    if (cd.get_lhs_type ())
> +    region_model_context *ctxt = cd.get_ctxt ();
> +    const gcall *call = cd.get_call_stmt ();
> +
> +    /* If the call is an allocating new, then create a heap
> allocated
> +    region.  */
> +    if (!is_placement_new_p (call))
> +      {

You have:
   if (!condition)
     suite_a;
   else
     suite_b; // this is implicitly a double negative
     

Please change it to:

  if (condition)
    suite_b;
  else
    suite_a;

to avoid the implicit double negative.


> +       const region *new_reg
> +         = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
> +       if (cd.get_lhs_type ())
> +         {
> +           const svalue *ptr_sval
> +             = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
> +           cd.maybe_set_lhs (ptr_sval);
> +         }
> +      }
> +    /* If the call was actually a placement new, check that
> accessing
> +    the buffer lhs is placed into does not result in out-of-bounds. 
> */
> +    else
>        {
> +       const region *ptr_reg = cd.maybe_get_arg_region (1);
> +       if (ptr_reg && cd.get_lhs_type ())
> +         {
> +           const region *base_reg = ptr_reg->get_base_region ();
> +           const svalue *num_bytes_sval = cd.get_arg_svalue (0);
> +           const region *sized_new_reg = mgr->get_sized_region (base_reg,
> +             cd.get_lhs_type (),
> +             num_bytes_sval);
> +           model->check_region_for_write (sized_new_reg,
> +             nullptr,
> +             ctxt);
>         const svalue *ptr_sval
> -         = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
> +         = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_new_reg);
>         cd.maybe_set_lhs (ptr_sval);
> +         }
> +      }
> +  }

[...snip...]

> diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C
> b/gcc/testsuite/g++.dg/analyzer/new-2.C
> new file mode 100644
> index 00000000000..4e696040a54
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
> @@ -0,0 +1,50 @@
> +// { dg-additional-options "-O0" }
> +
> +struct A
> +{
> +  int x;
> +  int y;
> +};

We've run into issues with bounds-checking testcases when using types
like "int" that have target-specific sizes.

Please use <stdint.h> in these test cases, and types with explicit
sizes, such as int32_t, to avoid the behavior of the test cases being
affected by sizeof the various types.

[..snip...]

Other than those issues, looks good

Thanks again
Dave


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
  2023-07-05 20:59 ` David Malcolm
@ 2023-07-05 22:01   ` Benjamin Priour
  0 siblings, 0 replies; 7+ messages in thread
From: Benjamin Priour @ 2023-07-05 22:01 UTC (permalink / raw)
  To: David Malcolm, gcc-patches

Hi David,

On 05/07/2023 22:59, David Malcolm wrote:
>> diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
>> index 393b4f25e79..258d92919d7 100644
>> --- a/gcc/analyzer/kf-lang-cp.cc
>> +++ b/gcc/analyzer/kf-lang-cp.cc
>> @@ -35,6 +35,34 @@ along with GCC; see the file COPYING3.  If not see
>>   
>>   #if ENABLE_ANALYZER
>>   
>> +/* Return TRUE if CALL is non-allocating operator new or operator
>> new[]*/
>> +
>> +bool is_placement_new_p (const gcall *call)
> Please can you extend the leading comment, giving the expected
> signatures of the functions, and a link to cppreference.org.
>
> In particular, there's some special-casing here of "nothrow_t" which
> would make more sense with a comment up here.

I've now extended the leading comment of is_placement_new_p so that the 
special cases appears clearer.

Leading comment is now:

    /* Return true if CALL is a non-allocating operator new or operator
    new []
       that contains no user-defined args, i.e. having any signature of:

         - void* operator new  ( std::size_t count, void* ptr );
         - void* operator new[]( std::size_t count, void* ptr );

       See https://en.cppreference.com/w/cpp/memory/new/operator_new . */

Whereas above the "nothrow_t" special case now reads

         /* We must distinguish between an allocating non-throwing new
         and a non-allocating new.

         The former might have one of the following signatures :
         void* operator new  ( std::size_t count, const std::nothrow_t&
    tag );
         void* operator new[]( std::size_t count, const std::nothrow_t&
    tag );

         However, debugging has shown that TAG is actually a POINTER_TYPE,
         not a REFERENCE_TYPE.

         Thus, we cannot easily differentiate the types, but we instead
    have to
         check if the second argument's type identifies as nothrow_t.  */


>> +{
>> +  gcc_assert (call);
>> +
>> +  tree fndecl = gimple_call_fndecl (call);
>> +  if (!fndecl)
>> +    return false;
>> +
>> +  if (!is_named_call_p (fndecl, "operator new", call, 2)
>> +    && !is_named_call_p (fndecl, "operator new []", call, 2))
>> +    return false;
>> +  tree arg1 = gimple_call_arg (call, 1);
>> +
>> +  if (!POINTER_TYPE_P (TREE_TYPE (arg1)))
>> +    return false;
>> +
>> +  /* Sadly, for non-throwing new, the second argument type
>> +    is not REFERENCE_TYPE but also POINTER_TYPE
>> +    so a simple check is out of the way.  */
>> +  tree identifier = TYPE_IDENTIFIER (TREE_TYPE (TREE_TYPE (arg1)));
>> +  if (!identifier)
>> +    return true;
>> +  const char *name = IDENTIFIER_POINTER (identifier);
>> +  return 0 != strcmp (name, "nothrow_t");
>> +}
>> +
>>   namespace ana {
>>   
>>   /* Implementations of specific functions.  */
>> @@ -46,7 +74,7 @@ class kf_operator_new : public known_function
>>   public:
>>     bool matches_call_types_p (const call_details &cd) const final
>> override
>>     {
>> -    return cd.num_args () == 1;
>> +    return cd.num_args () == 1 || cd.num_args () == 2;
> Looks like we should also check that arg 0 is of integral type, and
> that arg 1 is of pointer type.

Well technically some standard signatures use an align_val_t as a second 
argument,

which is a size_t value. But since we don't handle such signatures 
properly yet, I'm going

with your suggestion.

>>     }
>>   
>>     void impl_call_pre (const call_details &cd) const final override
>> @@ -54,13 +82,60 @@ public:
>>       region_model *model = cd.get_model ();
>>       region_model_manager *mgr = cd.get_manager ();
>>       const svalue *size_sval = cd.get_arg_svalue (0);
>> -    const region *new_reg
>> -      = model->get_or_create_region_for_heap_alloc (size_sval,
>> cd.get_ctxt ());
>> -    if (cd.get_lhs_type ())
>> +    region_model_context *ctxt = cd.get_ctxt ();
>> +    const gcall *call = cd.get_call_stmt ();
>> +
>> +    /* If the call is an allocating new, then create a heap
>> allocated
>> +    region.  */
>> +    if (!is_placement_new_p (call))
>> +      {
> You have:
>     if (!condition)
>       suite_a;
>     else
>       suite_b; // this is implicitly a double negative
>       
>
> Please change it to:
>
>    if (condition)
>      suite_b;
>    else
>      suite_a;
>
> to avoid the implicit double negative.
>
>
(nods)
>> diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C
>> b/gcc/testsuite/g++.dg/analyzer/new-2.C
>> new file mode 100644
>> index 00000000000..4e696040a54
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
>> @@ -0,0 +1,50 @@
>> +// { dg-additional-options "-O0" }
>> +
>> +struct A
>> +{
>> +  int x;
>> +  int y;
>> +};
> We've run into issues with bounds-checking testcases when using types
> like "int" that have target-specific sizes.
>
> Please use <stdint.h> in these test cases, and types with explicit
> sizes, such as int32_t, to avoid the behavior of the test cases being
> affected by sizeof the various types.
Thanks, I've now changed it in placement-new-size.C
>
> [..snip...]
>
> Other than those issues, looks good
>
> Thanks again
> Dave
>
Thanks for the review !

I'll submit the updated patch tomorrow on the mail list.

Benjamin


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
  2023-07-31 11:46   ` Benjamin Priour
@ 2023-07-31 22:53     ` David Malcolm
  0 siblings, 0 replies; 7+ messages in thread
From: David Malcolm @ 2023-07-31 22:53 UTC (permalink / raw)
  To: Benjamin Priour; +Cc: gcc-patches

On Mon, 2023-07-31 at 13:46 +0200, Benjamin Priour wrote:
> Hi Dave,
> 
> On Fri, Jul 21, 2023 at 10:10 PM David Malcolm <dmalcolm@redhat.com>
> wrote:

[...snip...]

> > 
> > I see that we have test coverage for:
> >   noexcept-new.C: -fno-exceptions with new vs nothrow-new
> > whereas:
> >   new-2.C has (implicitly) -fexceptions with new
> > 
> > It seems that of the four combinations for:
> >   - exceptions enabled or disabled
> > and:
> >   - throwing versus non-throwing new
> > this is covering three of the cases but is missing the case of
> > nothrow-
> > new when exceptions are enabled.
> > Presumably new-2.C should gain test coverage for this case.  Or am
> > I
> > missing something here?  Am I right in thinking that it's possible
> > for
> > the user to use nothrow new when exceptions are enabled to get a
> > new
> > that can fail and return nullptr?  Or is that not possible?
> > 
> > 
> Thanks a lot for spotting that, the new test pointed out an issue
> with the
> detection of nothrow.
> It has been fixed and now both test cases behave similarly.
> However, this highlighted a faulty test case I had written.
> 
> int* y = new(std::nothrow) int();
> int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
> /* { dg-warning "use of uninitialized value '\\*y'" "" { xfail *-*-*
> } .-1
> } */ // (#) should be a bogus
> delete y;
> 
> The test labelled (#) is wrong and should be a bogus instead.

Am I right in thinking that by this you mean that with the patch, the
analyzer complains about "use of uninitialized value '*y'" ? (which
would be an incorrect warning)

> If "y" is null then the allocation failed and dereferencing "y" will
> cause
> a segfault, not a "use-of-uninitialized-value".
> Thus we should stick to 'dereference of NULL 'y'" only.
> If "y" is non-null then the allocation succeeded and "*y" is
> initialized
> since we are calling a default initialization with the empty
> parenthesis.

I *think* it's possible to have the region_model have y pointing to a
heap_allocated_region of sizeof(int) size that's been initialized, but
still have the malloc state machine part of the program_state say that
the pointer is maybe-null.

What does the gimple look like and what does the program_state look
like after the assignment to y?

> 
> This led me to consider having "null-dereference" supersedes
> "use-of-uninitialized-value", but
> new PR 110830 made me reexamine it.
> 
> I believe fixing PR 110830 is thus required before submitting this
> patch,
> or we would have some extra irrelevant warnings.

How bad would the problem be?  PR 110830 looks a little involved, so is
there a way to get the current patch in without dragging that extra
complexity in?


[...snip...]

Thanks
Dave


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
  2023-07-21 20:10 ` David Malcolm
@ 2023-07-31 11:46   ` Benjamin Priour
  2023-07-31 22:53     ` David Malcolm
  0 siblings, 1 reply; 7+ messages in thread
From: Benjamin Priour @ 2023-07-31 11:46 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 6869 bytes --]

Hi Dave,

On Fri, Jul 21, 2023 at 10:10 PM David Malcolm <dmalcolm@redhat.com> wrote:
 [...]

It looks like something's gone wrong with the indentation in the above:
> previously we had tab characters, but now I'm seeing a pair of spaces,
> which means this wouldn't line up properly.  This might be a glitch
> somewhere in our email workflow, but please check it in your editor
> (with visual whitespace enabled).
>

I'll double check that before submitting.


> [...snip...]
>
> Some comments on the test cases:
>
> > diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C
> b/gcc/testsuite/g++.dg/analyzer/new-2.C
> > new file mode 100644
> > index 00000000000..4e696040a54
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
> > @@ -0,0 +1,50 @@
> > +// { dg-additional-options "-O0" }
> > +
> > +struct A
> > +{
> > +  int x;
> > +  int y;
> > +};
> > +
> > +void test_spurious_null_warning_throwing ()
> > +{
> > +  int *x = new int; /* { dg-bogus "dereference of possibly-NULL" } */
> > +  int *y = new int (); /* { dg-bogus "dereference of possibly-NULL"
> "non-throwing" } */
> > +  int *arr = new int[3]; /* { dg-bogus "dereference of possibly-NULL" }
> */
> > +  A *a = new A (); /* { dg-bogus "dereference of possibly-NULL"
> "throwing new cannot be null" } */
> > +
> > +  int z = *y + 2;
> > +  z = *x + 4; /* { dg-bogus "dereference of possibly-NULL 'x'" } */
> > +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-*
> } .-1 } */
> > +  z = arr[0] + 4; /* { dg-bogus "dereference of possibly-NULL" } */
> > +
> > +  delete a;
> > +  delete y;
> > +  delete x;
> > +  delete[] arr;
> > +}
> > +
> > +void test_default_initialization ()
> > +{
> > +    int *y = ::new int;
> > +    int *x = ::new int (); /* { dg-bogus "dereference of possibly-NULL
> 'operator new" } */
> > +
> > +    int b = *x + 3; /* { dg-bogus "dereference of possibly-NULL" } */
> > +    /* { dg-bogus "use of uninitialized ‘*x’" "" { target *-*-* } .-1 }
> */
> > +    int a = *y + 2; /* { dg-bogus "dereference of possibly-NULL 'y'" }
> */
> > +    /* { dg-warning "use of uninitialized value '\\*y'" "no default
> init" { target *-*-* } .-1 } */
> > +
> > +    delete x;
> > +    delete y;
> > +}
> > +
> > +/* From clang core.uninitialized.NewArraySize
> > +new[] should not be called with an undefined size argument */
> > +
> > +void test_garbage_new_array ()
> > +{
> > +  int n;
> > +  int *arr = ::new int[n]; /* { dg-warning "use of uninitialized value
> 'n'" } */
> > +  arr[0] = 7;
> > +  ::delete[] arr; /* no warnings emitted here either */
> > +}
> > diff --git a/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
> b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
> > new file mode 100644
> > index 00000000000..7699cd99cff
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
> > @@ -0,0 +1,48 @@
> > +/* { dg-additional-options "-O0 -fno-exceptions
> -fno-analyzer-suppress-followups" } */
> > +#include <new>
> > +
> > +/* Test non-throwing variants of operator new */
> > +
> > +struct A
> > +{
> > +  int x;
> > +  int y;
> > +};
> > +
> > +void test_throwing ()
> > +{
> > +  int* x = new int;
> > +  int* y = new int(); /* { dg-warning "dereference of possibly-NULL" }
> */
> > +  int* arr = new int[10];
> > +  A *a = new A(); /* { dg-warning "dereference of possibly-NULL" } */
> > +
> > +  int z = *y + 2;
> > +  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
> > +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-*
> } .-1 } */
> > +  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'"
> } */
> > +  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target
> *-*-* } .-1 } */
> > +  a->y = a->x + 3;
> > +
> > +  delete a;
> > +  delete y;
> > +  delete x;
> > +  delete[] arr;
> > +}
> > +
> > +void test_nonthrowing ()
> > +{
> > +  int* x = new(std::nothrow) int;
> > +  int* y = new(std::nothrow) int();
> > +  int* arr = new(std::nothrow) int[10];
> > +
> > +  int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
> > +  /* { dg-warning "use of uninitialized value '\\*y'" "" { target *-*-*
> } .-1 } */
> > +  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
> > +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-*
> } .-1 } */
> > +  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'"
> } */
> > +  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target
> *-*-* } .-1 } */
> > +
> > +  delete y;
> > +  delete x;
> > +  delete[] arr;
> > +}
>
> I see that we have test coverage for:
>   noexcept-new.C: -fno-exceptions with new vs nothrow-new
> whereas:
>   new-2.C has (implicitly) -fexceptions with new
>
> It seems that of the four combinations for:
>   - exceptions enabled or disabled
> and:
>   - throwing versus non-throwing new
> this is covering three of the cases but is missing the case of nothrow-
> new when exceptions are enabled.
> Presumably new-2.C should gain test coverage for this case.  Or am I
> missing something here?  Am I right in thinking that it's possible for
> the user to use nothrow new when exceptions are enabled to get a new
> that can fail and return nullptr?  Or is that not possible?
>
>
Thanks a lot for spotting that, the new test pointed out an issue with the
detection of nothrow.
It has been fixed and now both test cases behave similarly.
However, this highlighted a faulty test case I had written.

int* y = new(std::nothrow) int();
int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
/* { dg-warning "use of uninitialized value '\\*y'" "" { xfail *-*-* } .-1
} */ // (#) should be a bogus
delete y;

The test labelled (#) is wrong and should be a bogus instead.
If "y" is null then the allocation failed and dereferencing "y" will cause
a segfault, not a "use-of-uninitialized-value".
Thus we should stick to 'dereference of NULL 'y'" only.
If "y" is non-null then the allocation succeeded and "*y" is initialized
since we are calling a default initialization with the empty parenthesis.

This led me to consider having "null-dereference" supersedes
"use-of-uninitialized-value", but
new PR 110830 made me reexamine it.

I believe fixing PR 110830 is thus required before submitting this patch,
or we would have some extra irrelevant warnings.

I've also fixed the wording of "use-after-free" when using a placement
pointer after deallocation of its underlying buffer.

A *a = ::new A ();
int *lp = ::new (&a->y) int;
delete a;
int m = *lp; /* { dg-bogus "use after 'free' of 'lp'" "PREVIOUSLY" } */
int m = *lp; /* { dg-warning "use after 'delete' of 'lp'" "CURRENTLY" } */

Thanks
Benjamin

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
  2023-07-06 14:43 priour.be
@ 2023-07-21 20:10 ` David Malcolm
  2023-07-31 11:46   ` Benjamin Priour
  0 siblings, 1 reply; 7+ messages in thread
From: David Malcolm @ 2023-07-21 20:10 UTC (permalink / raw)
  To: priour.be, gcc-patches

On Thu, 2023-07-06 at 16:43 +0200, priour.be@gmail.com wrote:
> As per David's suggestion.
> - Improved leading comment of "is_placement_new_p"
> - "kf_operator_new::matches_call_types_p" now checks that arg 0 is of
>   integral type and that arg 1, if any, is of pointer type.
> - Changed ambiguous "int" to "int8_t" and "int64_t" in placement-new-
> size.C
>   to trigger a target independent out-of-bounds warning.
>   Other OOB tests were not based on the size of types, but on the
> number
>   elements, so them using "int" didn't lead to any ambiguity.
> 
> contrib/check_GNU_style.sh still complains about a space before
> square
> brackets in string "operator new []", but as before, this one space
> is
> mandatory for a correct recognition of the function.
> 
> Changes succesfully regstrapped on x86_64-linux-gnu against trunk
> 3c776fdf1a8.
> 
> Is it OK for trunk ?
> Thanks again,
> Benjamin.

Hi Benjamin, thanks for the updated patch.

As before, this looks close to being ready, but I have some further
comments:

[...snip...]

> diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
> index 393b4f25e79..ef057da863f 100644
> --- a/gcc/analyzer/kf-lang-cp.cc
> +++ b/gcc/analyzer/kf-lang-cp.cc
> @@ -35,6 +35,49 @@ along with GCC; see the file COPYING3.  If not see
>  
>  #if ENABLE_ANALYZER
>  
> +/* Return true if CALL is a non-allocating operator new or operator new []
> +  that contains no user-defined args, i.e. having any signature of:
> +
> +    - void* operator new (std::size_t count, void* ptr);
> +    - void* operator new[] (std::size_t count, void* ptr);
> +
> +  See https://en.cppreference.com/w/cpp/memory/new/operator_new.  */
> +
> +bool is_placement_new_p (const gcall *call)
> +{
> +  gcc_assert (call);
> +
> +  tree fndecl = gimple_call_fndecl (call);
> +  if (!fndecl)
> +    return false;
> +
> +  if (!is_named_call_p (fndecl, "operator new", call, 2)
> +    && !is_named_call_p (fndecl, "operator new []", call, 2))
> +    return false;
> +  tree arg1 = gimple_call_arg (call, 1);
> +
> +  if (!POINTER_TYPE_P (TREE_TYPE (arg1)))
> +    return false;
> +
> +  /* We must distinguish between an allocating non-throwing new
> +    and a non-allocating new.
> +
> +    The former might have one of the following signatures :
> +    void* operator new (std::size_t count, const std::nothrow_t& tag);
> +    void* operator new[] (std::size_t count, const std::nothrow_t& tag);
> +
> +    However, debugging has shown that TAG is actually a POINTER_TYPE,
> +    not a REFERENCE_TYPE.
> +
> +    Thus, we cannot easily differentiate the types, but we instead have to
> +    check if the second argument's type identifies as nothrow_t.  */
> +  tree identifier = TYPE_IDENTIFIER (TREE_TYPE (TREE_TYPE (arg1)));
> +  if (!identifier)
> +    return true;
> +  const char *name = IDENTIFIER_POINTER (identifier);
> +  return 0 != strcmp (name, "nothrow_t");
> +}
> +

If we're looking for a simple "void *", wouldn't it be simpler and
cleaner to check for arg1 being a pointer to a type that's VOID_TYPE_P,
rather than this name comparison?

[...snip...]

> diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
> index a8c63eb1ce8..41c313c07dd 100644
> --- a/gcc/analyzer/sm-malloc.cc
> +++ b/gcc/analyzer/sm-malloc.cc
> @@ -754,7 +754,7 @@ public:
>      override
>    {
>      if (change.m_old_state == m_sm.get_start_state ()
> -	&& unchecked_p (change.m_new_state))
> +	&& (unchecked_p (change.m_new_state) || nonnull_p (change.m_new_state)))
>        // TODO: verify that it's the allocation stmt, not a copy
>        return label_text::borrow ("allocated here");
>      if (unchecked_p (change.m_old_state)
> @@ -1910,11 +1910,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
>  	    return true;
>  	  }
>  
> -	if (is_named_call_p (callee_fndecl, "operator new", call, 1))
> -	  on_allocator_call (sm_ctxt, 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);
> -	else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
> +	if (!is_placement_new_p (call))
> +	  {
> +  bool returns_nonnull = !TREE_NOTHROW (callee_fndecl) && flag_exceptions;
> +  if (is_named_call_p (callee_fndecl, "operator new"))
> +    on_allocator_call (sm_ctxt, call, &m_scalar_delete, returns_nonnull);
> +  else if (is_named_call_p (callee_fndecl, "operator new []"))
> +    on_allocator_call (sm_ctxt, call, &m_vector_delete, returns_nonnull);
> +	  }
> +
> +	if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
>  		 || is_named_call_p (callee_fndecl, "operator delete", call, 2))
>  	  {
>  	    on_deallocator_call (sm_ctxt, node, call,

It looks like something's gone wrong with the indentation in the above:
previously we had tab characters, but now I'm seeing a pair of spaces,
which means this wouldn't line up properly.  This might be a glitch
somewhere in our email workflow, but please check it in your editor
(with visual whitespace enabled).

[...snip...]

Some comments on the test cases:

> diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C
b/gcc/testsuite/g++.dg/analyzer/new-2.C
> new file mode 100644
> index 00000000000..4e696040a54
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
> @@ -0,0 +1,50 @@
> +// { dg-additional-options "-O0" }
> +
> +struct A
> +{
> +  int x;
> +  int y;
> +};
> +
> +void test_spurious_null_warning_throwing ()
> +{
> +  int *x = new int; /* { dg-bogus "dereference of possibly-NULL" } */
> +  int *y = new int (); /* { dg-bogus "dereference of possibly-NULL" "non-throwing" } */
> +  int *arr = new int[3]; /* { dg-bogus "dereference of possibly-NULL" } */ 
> +  A *a = new A (); /* { dg-bogus "dereference of possibly-NULL" "throwing new cannot be null" } */
> +
> +  int z = *y + 2;
> +  z = *x + 4; /* { dg-bogus "dereference of possibly-NULL 'x'" } */
> +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
> +  z = arr[0] + 4; /* { dg-bogus "dereference of possibly-NULL" } */
> +
> +  delete a;
> +  delete y;
> +  delete x;
> +  delete[] arr;
> +}
> +
> +void test_default_initialization ()
> +{
> +    int *y = ::new int;
> +    int *x = ::new int (); /* { dg-bogus "dereference of possibly-NULL 'operator new" } */
> +
> +    int b = *x + 3; /* { dg-bogus "dereference of possibly-NULL" } */
> +    /* { dg-bogus "use of uninitialized ‘*x’" "" { target *-*-* } .-1 } */
> +    int a = *y + 2; /* { dg-bogus "dereference of possibly-NULL 'y'" } */
> +    /* { dg-warning "use of uninitialized value '\\*y'" "no default init" { target *-*-* } .-1 } */
> +
> +    delete x;
> +    delete y;
> +}
> +
> +/* From clang core.uninitialized.NewArraySize
> +new[] should not be called with an undefined size argument */
> +
> +void test_garbage_new_array ()
> +{
> +  int n;
> +  int *arr = ::new int[n]; /* { dg-warning "use of uninitialized value 'n'" } */
> +  arr[0] = 7;
> +  ::delete[] arr; /* no warnings emitted here either */
> +}
> diff --git a/gcc/testsuite/g++.dg/analyzer/noexcept-new.C b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
> new file mode 100644
> index 00000000000..7699cd99cff
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
> @@ -0,0 +1,48 @@
> +/* { dg-additional-options "-O0 -fno-exceptions -fno-analyzer-suppress-followups" } */
> +#include <new>
> +
> +/* Test non-throwing variants of operator new */
> +
> +struct A
> +{
> +  int x;
> +  int y;
> +};
> +
> +void test_throwing ()
> +{
> +  int* x = new int;
> +  int* y = new int(); /* { dg-warning "dereference of possibly-NULL" } */
> +  int* arr = new int[10];
> +  A *a = new A(); /* { dg-warning "dereference of possibly-NULL" } */
> +
> +  int z = *y + 2;
> +  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
> +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
> +  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
> +  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
> +  a->y = a->x + 3;
> +
> +  delete a;
> +  delete y;
> +  delete x;
> +  delete[] arr;
> +}
> +
> +void test_nonthrowing ()
> +{
> +  int* x = new(std::nothrow) int;
> +  int* y = new(std::nothrow) int();
> +  int* arr = new(std::nothrow) int[10];
> +
> +  int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
> +  /* { dg-warning "use of uninitialized value '\\*y'" "" { target *-*-* } .-1 } */
> +  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
> +  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
> +  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
> +  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
> +
> +  delete y;
> +  delete x;
> +  delete[] arr;
> +}

I see that we have test coverage for:
  noexcept-new.C: -fno-exceptions with new vs nothrow-new
whereas:
  new-2.C has (implicitly) -fexceptions with new

It seems that of the four combinations for:
  - exceptions enabled or disabled
and:
  - throwing versus non-throwing new
this is covering three of the cases but is missing the case of nothrow-
new when exceptions are enabled.
Presumably new-2.C should gain test coverage for this case.  Or am I
missing something here?  Am I right in thinking that it's possible for
the user to use nothrow new when exceptions are enabled to get a new
that can fail and return nullptr?  Or is that not possible?

[...snip...]

Hope this makes sense; thanks again for the patch
Dave


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] analyzer: Add support of placement new and improved operator new [PR105948]
@ 2023-07-06 14:43 priour.be
  2023-07-21 20:10 ` David Malcolm
  0 siblings, 1 reply; 7+ messages in thread
From: priour.be @ 2023-07-06 14:43 UTC (permalink / raw)
  To: gcc-patches; +Cc: dmalcolm, benjamin priour

As per David's suggestion.
- Improved leading comment of "is_placement_new_p"
- "kf_operator_new::matches_call_types_p" now checks that arg 0 is of
  integral type and that arg 1, if any, is of pointer type.
- Changed ambiguous "int" to "int8_t" and "int64_t" in placement-new-size.C
  to trigger a target independent out-of-bounds warning.
  Other OOB tests were not based on the size of types, but on the number
  elements, so them using "int" didn't lead to any ambiguity.

contrib/check_GNU_style.sh still complains about a space before square
brackets in string "operator new []", but as before, this one space is
mandatory for a correct recognition of the function.

Changes succesfully regstrapped on x86_64-linux-gnu against trunk
3c776fdf1a8.

Is it OK for trunk ?
Thanks again,
Benjamin.

---

Fixed spurious possibly-NULL warning always tagging along throwing
operator new despite it never returning NULL.
Now, operator new is correctly recognized as possibly returning
NULL if and only if it is non-throwing or exceptions have been disabled.
Different standard signatures of operator new are now properly recognized.

Added support of placement new, so that is now properly recognized,
and a 'heap_allocated' region is no longer created for it.
Placement new size is also checked and a 'Wanalyzer-allocation-size'
is emitted when relevant, as well as always a 'Wanalyzer-out-of-bounds'.

gcc/analyzer/ChangeLog:

	PR analyzer/105948
	* analyzer.h (is_placement_new_p): New declaration.
	* call-details.cc
	(call_details::maybe_get_arg_region): New function.
	Returns the region of the argument at given index if possible.
	* call-details.h: Declaration of above function.
	* kf-lang-cp.cc (is_placement_new_p): Returns true if the
	gcall is recognized as a placement new.
	* region-model.cc (region_model::eval_condition):
	Now recursively call itself if one the operand is wrapped in a cast.
	* sm-malloc.cc (malloc_state_machine::on_stmt): Added
	recognition of placement new.

gcc/testsuite/ChangeLog:

	PR analyzer/105948
	* g++.dg/analyzer/out-of-bounds-placement-new.C: Added a directive.
	* g++.dg/analyzer/placement-new.C: Added tests.
	* g++.dg/analyzer/new-2.C: New test.
	* g++.dg/analyzer/noexcept-new.C: New test.
	* g++.dg/analyzer/placement-new-size.C: New test.

Signed-off-by: benjamin priour <priour.be@gmail.com>
---
 gcc/analyzer/analyzer.h                       |   1 +
 gcc/analyzer/call-details.cc                  |  11 ++
 gcc/analyzer/call-details.h                   |   1 +
 gcc/analyzer/kf-lang-cp.cc                    | 105 +++++++++++++++++-
 gcc/analyzer/region-model.cc                  |  21 ++++
 gcc/analyzer/sm-malloc.cc                     |  17 ++-
 gcc/testsuite/g++.dg/analyzer/new-2.C         |  50 +++++++++
 gcc/testsuite/g++.dg/analyzer/noexcept-new.C  |  48 ++++++++
 .../analyzer/out-of-bounds-placement-new.C    |   2 +-
 .../g++.dg/analyzer/placement-new-size.C      |  27 +++++
 gcc/testsuite/g++.dg/analyzer/placement-new.C |  63 ++++++++++-
 11 files changed, 332 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/analyzer/new-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/noexcept-new.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/placement-new-size.C

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 579517c23e6..b86e5cac74d 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -391,6 +391,7 @@ extern bool is_std_named_call_p (const_tree fndecl, const char *funcname,
 				 const gcall *call, unsigned int num_args);
 extern bool is_setjmp_call_p (const gcall *call);
 extern bool is_longjmp_call_p (const gcall *call);
+extern bool is_placement_new_p (const gcall *call);
 
 extern const char *get_user_facing_name (const gcall *call);
 
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index 17edaf26276..01f061d774e 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -152,6 +152,17 @@ call_details::get_arg_svalue (unsigned idx) const
   return m_model->get_rvalue (arg, m_ctxt);
 }
 
+/* If argument IDX's svalue at the callsite is a region_svalue,
+   return the region it points to.
+   Otherwise return NULL.  */
+
+const region *
+call_details::maybe_get_arg_region (unsigned idx) const
+{
+  const svalue *sval = get_arg_svalue (idx);
+  return sval->maybe_get_region ();
+}
+
 /* Attempt to get the string literal for argument IDX, or return NULL
    otherwise.
    For use when implementing "__analyzer_*" functions that take
diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h
index 14a206ff5d6..aac2b7d33d8 100644
--- a/gcc/analyzer/call-details.h
+++ b/gcc/analyzer/call-details.h
@@ -55,6 +55,7 @@ public:
   tree get_arg_tree (unsigned idx) const;
   tree get_arg_type (unsigned idx) const;
   const svalue *get_arg_svalue (unsigned idx) const;
+  const region *maybe_get_arg_region (unsigned idx) const;
   const char *get_arg_string_literal (unsigned idx) const;
 
   tree get_fndecl_for_call () const;
diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
index 393b4f25e79..ef057da863f 100644
--- a/gcc/analyzer/kf-lang-cp.cc
+++ b/gcc/analyzer/kf-lang-cp.cc
@@ -35,6 +35,49 @@ along with GCC; see the file COPYING3.  If not see
 
 #if ENABLE_ANALYZER
 
+/* Return true if CALL is a non-allocating operator new or operator new []
+  that contains no user-defined args, i.e. having any signature of:
+
+    - void* operator new (std::size_t count, void* ptr);
+    - void* operator new[] (std::size_t count, void* ptr);
+
+  See https://en.cppreference.com/w/cpp/memory/new/operator_new.  */
+
+bool is_placement_new_p (const gcall *call)
+{
+  gcc_assert (call);
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return false;
+
+  if (!is_named_call_p (fndecl, "operator new", call, 2)
+    && !is_named_call_p (fndecl, "operator new []", call, 2))
+    return false;
+  tree arg1 = gimple_call_arg (call, 1);
+
+  if (!POINTER_TYPE_P (TREE_TYPE (arg1)))
+    return false;
+
+  /* We must distinguish between an allocating non-throwing new
+    and a non-allocating new.
+
+    The former might have one of the following signatures :
+    void* operator new (std::size_t count, const std::nothrow_t& tag);
+    void* operator new[] (std::size_t count, const std::nothrow_t& tag);
+
+    However, debugging has shown that TAG is actually a POINTER_TYPE,
+    not a REFERENCE_TYPE.
+
+    Thus, we cannot easily differentiate the types, but we instead have to
+    check if the second argument's type identifies as nothrow_t.  */
+  tree identifier = TYPE_IDENTIFIER (TREE_TYPE (TREE_TYPE (arg1)));
+  if (!identifier)
+    return true;
+  const char *name = IDENTIFIER_POINTER (identifier);
+  return 0 != strcmp (name, "nothrow_t");
+}
+
 namespace ana {
 
 /* Implementations of specific functions.  */
@@ -46,7 +89,11 @@ class kf_operator_new : public known_function
 public:
   bool matches_call_types_p (const call_details &cd) const final override
   {
-    return cd.num_args () == 1;
+    return (cd.num_args () == 1
+      && cd.arg_is_size_p (0))
+      || (cd.num_args () == 2
+      && cd.arg_is_size_p (0)
+      && POINTER_TYPE_P (cd.get_arg_type (1)));
   }
 
   void impl_call_pre (const call_details &cd) const final override
@@ -54,13 +101,61 @@ public:
     region_model *model = cd.get_model ();
     region_model_manager *mgr = cd.get_manager ();
     const svalue *size_sval = cd.get_arg_svalue (0);
-    const region *new_reg
-      = model->get_or_create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
-    if (cd.get_lhs_type ())
+    region_model_context *ctxt = cd.get_ctxt ();
+    const gcall *call = cd.get_call_stmt ();
+
+    /* If the call was actually a placement new, check that accessing
+    the buffer lhs is placed into does not result in out-of-bounds.  */
+    if (is_placement_new_p (call))
       {
+	const region *ptr_reg = cd.maybe_get_arg_region (1);
+	if (ptr_reg && cd.get_lhs_type ())
+	  {
+	    const region *base_reg = ptr_reg->get_base_region ();
+	    const svalue *num_bytes_sval = cd.get_arg_svalue (0);
+	    const region *sized_new_reg = mgr->get_sized_region (base_reg,
+	      cd.get_lhs_type (),
+	      num_bytes_sval);
+	    model->check_region_for_write (sized_new_reg,
+	      nullptr,
+	      ctxt);
 	const svalue *ptr_sval
-	  = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	  = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_new_reg);
 	cd.maybe_set_lhs (ptr_sval);
+	  }
+      }
+    /* If the call is an allocating new, then create a heap allocated
+    region.  */
+    else
+      {
+	const region *new_reg
+	  = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
+	if (cd.get_lhs_type ())
+	  {
+	    const svalue *ptr_sval
+	      = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	    cd.maybe_set_lhs (ptr_sval);
+	  }
+      }
+
+  }
+
+  void impl_call_post (const call_details &cd) const final override
+  {
+    region_model *model = cd.get_model ();
+    region_model_manager *mgr = cd.get_manager ();
+    tree callee_fndecl = cd.get_fndecl_for_call ();
+    region_model_context *ctxt = cd.get_ctxt ();
+
+    /* If the call is guaranteed to return nonnull
+      then add a nonnull constraint to the allocated region.  */
+    if (!TREE_NOTHROW (TREE_TYPE (callee_fndecl)) && flag_exceptions)
+      {
+	const svalue *nonnull
+	  = mgr->get_or_create_null_ptr (cd.get_lhs_type ());
+	const svalue *result
+	  = model->get_store_value (cd.get_lhs_region (), ctxt);
+	model->add_constraint (result, NE_EXPR, nonnull, ctxt);
       }
   }
 };
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 187013a37cc..599178e3d86 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -3479,6 +3479,27 @@ region_model::eval_condition (const svalue *lhs,
 	}
     }
 
+  /* Attempt to unwrap cast if there is one, and the types match.  */
+  tree lhs_type = lhs->get_type ();
+  tree rhs_type = rhs->get_type ();
+  if (lhs_type && rhs_type)
+  {
+    const unaryop_svalue *lhs_un_op = dyn_cast <const unaryop_svalue *> (lhs);
+    const unaryop_svalue *rhs_un_op = dyn_cast <const unaryop_svalue *> (rhs);
+    if (lhs_un_op && CONVERT_EXPR_CODE_P (lhs_un_op->get_op ())
+      && rhs_un_op && CONVERT_EXPR_CODE_P (rhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs_un_op->get_arg (), op, rhs_un_op->get_arg ());
+
+    else if (lhs_un_op && CONVERT_EXPR_CODE_P (lhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs_un_op->get_arg (), op, rhs);
+
+    else if (rhs_un_op && CONVERT_EXPR_CODE_P (rhs_un_op->get_op ())
+      && lhs_type == rhs_type)
+      return eval_condition (lhs, op, rhs_un_op->get_arg ());
+  }
+
   /* Otherwise, try constraints.
      Cast to const to ensure we don't change the constraint_manager as we
      do this (e.g. by creating equivalence classes).  */
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index a8c63eb1ce8..41c313c07dd 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -754,7 +754,7 @@ public:
     override
   {
     if (change.m_old_state == m_sm.get_start_state ()
-	&& unchecked_p (change.m_new_state))
+	&& (unchecked_p (change.m_new_state) || nonnull_p (change.m_new_state)))
       // TODO: verify that it's the allocation stmt, not a copy
       return label_text::borrow ("allocated here");
     if (unchecked_p (change.m_old_state)
@@ -1910,11 +1910,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
 	    return true;
 	  }
 
-	if (is_named_call_p (callee_fndecl, "operator new", call, 1))
-	  on_allocator_call (sm_ctxt, 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);
-	else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
+	if (!is_placement_new_p (call))
+	  {
+  bool returns_nonnull = !TREE_NOTHROW (callee_fndecl) && flag_exceptions;
+  if (is_named_call_p (callee_fndecl, "operator new"))
+    on_allocator_call (sm_ctxt, call, &m_scalar_delete, returns_nonnull);
+  else if (is_named_call_p (callee_fndecl, "operator new []"))
+    on_allocator_call (sm_ctxt, call, &m_vector_delete, returns_nonnull);
+	  }
+
+	if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
 		 || is_named_call_p (callee_fndecl, "operator delete", call, 2))
 	  {
 	    on_deallocator_call (sm_ctxt, node, call,
diff --git a/gcc/testsuite/g++.dg/analyzer/new-2.C b/gcc/testsuite/g++.dg/analyzer/new-2.C
new file mode 100644
index 00000000000..4e696040a54
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/new-2.C
@@ -0,0 +1,50 @@
+// { dg-additional-options "-O0" }
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_spurious_null_warning_throwing ()
+{
+  int *x = new int; /* { dg-bogus "dereference of possibly-NULL" } */
+  int *y = new int (); /* { dg-bogus "dereference of possibly-NULL" "non-throwing" } */
+  int *arr = new int[3]; /* { dg-bogus "dereference of possibly-NULL" } */ 
+  A *a = new A (); /* { dg-bogus "dereference of possibly-NULL" "throwing new cannot be null" } */
+
+  int z = *y + 2;
+  z = *x + 4; /* { dg-bogus "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-bogus "dereference of possibly-NULL" } */
+
+  delete a;
+  delete y;
+  delete x;
+  delete[] arr;
+}
+
+void test_default_initialization ()
+{
+    int *y = ::new int;
+    int *x = ::new int (); /* { dg-bogus "dereference of possibly-NULL 'operator new" } */
+
+    int b = *x + 3; /* { dg-bogus "dereference of possibly-NULL" } */
+    /* { dg-bogus "use of uninitialized ‘*x’" "" { target *-*-* } .-1 } */
+    int a = *y + 2; /* { dg-bogus "dereference of possibly-NULL 'y'" } */
+    /* { dg-warning "use of uninitialized value '\\*y'" "no default init" { target *-*-* } .-1 } */
+
+    delete x;
+    delete y;
+}
+
+/* From clang core.uninitialized.NewArraySize
+new[] should not be called with an undefined size argument */
+
+void test_garbage_new_array ()
+{
+  int n;
+  int *arr = ::new int[n]; /* { dg-warning "use of uninitialized value 'n'" } */
+  arr[0] = 7;
+  ::delete[] arr; /* no warnings emitted here either */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/noexcept-new.C b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
new file mode 100644
index 00000000000..7699cd99cff
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/noexcept-new.C
@@ -0,0 +1,48 @@
+/* { dg-additional-options "-O0 -fno-exceptions -fno-analyzer-suppress-followups" } */
+#include <new>
+
+/* Test non-throwing variants of operator new */
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_throwing ()
+{
+  int* x = new int;
+  int* y = new int(); /* { dg-warning "dereference of possibly-NULL" } */
+  int* arr = new int[10];
+  A *a = new A(); /* { dg-warning "dereference of possibly-NULL" } */
+
+  int z = *y + 2;
+  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
+  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
+  a->y = a->x + 3;
+
+  delete a;
+  delete y;
+  delete x;
+  delete[] arr;
+}
+
+void test_nonthrowing ()
+{
+  int* x = new(std::nothrow) int;
+  int* y = new(std::nothrow) int();
+  int* arr = new(std::nothrow) int[10];
+
+  int z = *y + 2; /* { dg-warning "dereference of NULL 'y'" } */
+  /* { dg-warning "use of uninitialized value '\\*y'" "" { target *-*-* } .-1 } */
+  z = *x + 4; /* { dg-warning "dereference of possibly-NULL 'x'" } */
+  /* { dg-warning "use of uninitialized value '\\*x'" "" { target *-*-* } .-1 } */
+  z = arr[0] + 4; /* { dg-warning "dereference of possibly-NULL 'arr'" } */
+  /* { dg-warning "use of uninitialized value '\\*arr'" "" { target *-*-* } .-1 } */
+
+  delete y;
+  delete x;
+  delete[] arr;
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C b/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
index d3076c3cf25..33872e6ddab 100644
--- a/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
+++ b/gcc/testsuite/g++.dg/analyzer/out-of-bounds-placement-new.C
@@ -14,6 +14,6 @@ struct int_and_addr {
 
 int test (int_container ic)
 {
-  int_and_addr *iaddr = new (ic.addr ()) int_and_addr;
+  int_and_addr *iaddr = new (ic.addr ()) int_and_addr; /* { dg-warning "stack-based buffer overflow" } */
   return iaddr->i;
 }
diff --git a/gcc/testsuite/g++.dg/analyzer/placement-new-size.C b/gcc/testsuite/g++.dg/analyzer/placement-new-size.C
new file mode 100644
index 00000000000..4536d361b4a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/placement-new-size.C
@@ -0,0 +1,27 @@
+/* { dg-additional-options "-Wno-placement-new -Wno-analyzer-use-of-uninitialized-value" } */
+
+#include <new>
+#include <stdint.h>
+
+extern int get_buf_size ();
+
+void var_too_short ()
+{
+  int8_t s;
+  int64_t *lp = new (&s) int64_t; /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" "" { target *-*-* } .-1 } */
+}
+
+void static_buffer_too_short ()
+{
+  int n = 16;
+  int buf[n];
+  int *p = new (buf) int[n + 1]; /* { dg-warning "stack-based buffer overflow" } */
+}
+
+void symbolic_buffer_too_short ()
+{
+  int n = get_buf_size ();
+  char buf[n];
+  char *p = new (buf) char[n + 10]; /* { dg-warning "stack-based buffer overflow" } */
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/placement-new.C b/gcc/testsuite/g++.dg/analyzer/placement-new.C
index 89055491a48..c0cf7ec4c48 100644
--- a/gcc/testsuite/g++.dg/analyzer/placement-new.C
+++ b/gcc/testsuite/g++.dg/analyzer/placement-new.C
@@ -14,15 +14,74 @@ void test_2 (void)
 {
   char buf[sizeof(int) * 10];
   int *p = new(buf) int[10];
-}
+} // { dg-prune-output "-Wfree-nonheap-object" }
 
 /* Delete of placement new.  */
 
 void test_3 (void)
 {
   char buf[sizeof(int)]; // { dg-message "region created on stack here" }
-  int *p = new(buf) int (42);
+  int *p = new (buf) int (42);
   delete p; // { dg-warning "memory on the stack" }
 }
 
 // { dg-prune-output "-Wfree-nonheap-object" }
+
+void test_4 (void)
+{
+  int buf[5]; // { dg-message "region created on stack here" }
+  int *p = new (&buf[2]) int (42);
+  delete p; // { dg-warning "memory on the stack" }
+}
+
+
+// { dg-prune-output "-Wfree-nonheap-object" }
+
+void test_write_placement_after_delete (void)
+{
+  short *s = ::new short;
+  short *lp = ::new (s) short;
+  ::delete s;
+  *lp = 12; /* { dg-warning "use after 'delete' of 'lp'" "write placement new after buffer deletion" } */
+}
+
+void test_read_placement_after_delete (void)
+{
+  short *s = ::new short;
+  short *lp = ::new (s) short;
+  ::delete s;
+  short m = *lp; // { dg-warning "use after 'delete' of 'lp'" "read placement new after buffer deletion" }
+}
+
+struct A
+{
+  int x;
+  int y;
+};
+
+void test_use_placement_after_destruction (void)
+{
+  A a;
+  int *lp = ::new (&a.y) int;
+  *lp = 2; /* { dg-bogus "-Wanalyzer-use-of-uninitialized-value" } */
+  a.~A();
+  int m = *lp; /* { dg-warning "use of uninitialized value '\\*lp'" "use of placement after the underlying buffer was destructed." } */
+}
+
+void test_initialization_through_placement (void)
+{
+  int x;
+  int *p = ::new (&x) int;
+  *p = 10;
+  int z = x + 2; /* { dg-bogus "use of uninitialized value 'x'" "x has been initialized through placement pointer" } */
+}
+
+void test_partial_initialization_through_placement (void)
+{
+  char buf[4];
+  char *p = ::new (&buf[2]) char;
+  *p = 10;
+  char *y = ::new (&buf[0]) char;
+  char z = buf[2] + 2; /* { dg-bogus "use of uninitialized value" } */
+  z = *y + 2; /* { dg-warning "use of uninitialized value '\\*y'" "y has only been partially initialized" } */
+}
\ No newline at end of file
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-07-31 22:53 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-07-04 16:25 [PATCH] analyzer: Add support of placement new and improved operator new [PR105948] priour.be
2023-07-05 20:59 ` David Malcolm
2023-07-05 22:01   ` Benjamin Priour
2023-07-06 14:43 priour.be
2023-07-21 20:10 ` David Malcolm
2023-07-31 11:46   ` Benjamin Priour
2023-07-31 22:53     ` David Malcolm

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).