analyzer: Add support of placement new and improved operator new [PR105948]
Checks
Commit Message
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
Comments
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
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
@@ -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);
@@ -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
@@ -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;
@@ -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);
}
}
};
@@ -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). */
@@ -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,
new file mode 100644
@@ -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 */
+}
new file mode 100644
@@ -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;
+}
@@ -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;
}
new file mode 100644
@@ -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" } */
+}
@@ -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