On Thu, 2023-07-06 at 16:43 +0200, priour...@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