https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125223

            Bug ID: 125223
           Summary: Poor UX when analyzer reports on issues inside C++ std
                    types
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: analyzer
          Assignee: dmalcolm at gcc dot gnu.org
          Reporter: dmalcolm at gcc dot gnu.org
                CC: redi at gcc dot gnu.org
            Blocks: 97110
  Target Milestone: ---

Consider e.g.:
$ cat ../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C 

// { dg-do compile { target c++11 } }
// { dg-additional-options "-Wno-analyzer-too-complex" } */

#include <memory>

struct A {int x; int y;};

extern std::unique_ptr<A> make_ptr ();

int test (int flag) {
  std::unique_ptr<A> a;
  if (flag)
    a = make_ptr ();
  a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
  // TODO: this is failing due to "too complex" warnings
  return 0;
}

The analyzer finds the problem if we enable optimization, but the messages are
horrible.

At -O we get:

../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C: In function ‘int
test(int)’:
../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C:14:8: warning:
dereference of NULL ‘<unknown>’ [CWE-476] [-Wanalyzer-null-dereference]
   14 |   a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
      |   ~~~~~^~~~
  ‘int test(int)’: events 1-3
   12 |   if (flag)
      |   ^~
      |   |
      |   (1) following ‘false’ branch (when ‘flag == 0’)... ─>─┐
      |                                                         │
      |                                                         │
      |┌────────────────────────────────────────────────────────┘
   13 |│    a = make_ptr ();
   14 |│  a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
      |│  ~~~~~~~~~
      |│       |
      |└──────>(2) ...to here
      |        (3) ⚠️  dereference of NULL
‘a.std::unique_ptr<A>::_M_t.std::__uniq_ptr_data<A, std::default_delete<A>,
true, true>::std::__uniq_ptr_impl<A, std::default_delete<A>
>.std::__uniq_ptr_impl<A, std::default_delete<A> >::_M_t.std::tuple<A*,
std::default_delete<A> >::std::_Tuple_impl<0, A*, std::default_delete<A>
>.std::_Tuple_impl<0, A*, std::default_delete<A> >::std::_Head_base<0, A*,
false>.std::_Head_base<0, A*, false>::_M_head_impl’

At -Og we get:
../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C: In function ‘int
test(int)’:
../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C:14:8: warning:
dereference of NULL ‘0’ [CWE-476] [-Wanalyzer-null-dereference]
   14 |   a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
      |   ~~~~~^~~~
  ‘int test(int)’: events 1-4
    │
    │   10 | int test (int flag) {
    │      |     ^~~~
    │      |     |
    │      |     (1) entry to ‘test’
    │   11 |   std::unique_ptr<A> a;
    │      |                      ~
    │      |                      |
    │      |                      (2) using NULL here
    │   12 |   if (flag)
    │      |   ~~ 
    │      |   |
    │      |   (3) following ‘false’ branch (when ‘flag == 0’)... ─>─┐
    │      |                                                         │
    │      |                                                         │
    │      |┌────────────────────────────────────────────────────────┘
    │   13 |│    a = make_ptr ();
    │   14 |│  a->x = 12; // { dg-warning "dereference of NULL" "" { xfail
*-*-*} }
    │      |│   ~ 
    │      |│   |
    │      |└──>(4) inlined call to ‘std::unique_ptr<A>::operator->’ from
‘test’
    │
    └──> ‘std::unique_ptr<_Tp, _Dp>::pointer std::unique_ptr<_Tp,
_Dp>::operator->() const [with _Tp = A; _Dp = std::default_delete<A>]’: event 5
           │
          
│../x86_64-pc-linux-gnu/libstdc++-v3/include/bits/unique_ptr.h:484:19:
           │  484 |         return get();
           │      |                   ^
           │      |                   |
           │      |                   (5) inlined call to
‘std::unique_ptr<A>::get’ from ‘std::unique_ptr<A>::operator->’
           │
           └──> ‘std::unique_ptr<_Tp, _Dp>::pointer std::unique_ptr<_Tp,
_Dp>::get() const [with _Tp = A; _Dp = std::default_delete<A>]’: event 6
                  │
                  │  491 |       { return _M_t._M_ptr(); }
                  │      |                           ^
                  │      |                           |
                  │      |                           (6) inlined call to
‘std::__uniq_ptr_impl<A, std::default_delete<A> >::_M_ptr’ from
‘std::unique_ptr<A>::get’
                  │
                  └──> ‘std::__uniq_ptr_impl<_Tp, _Dp>::pointer
std::__uniq_ptr_impl<_Tp, _Dp>::_M_ptr() const [with _Tp = A; _Dp =
std::default_delete<A>]’: event 7
                         │
                         │  192 |       pointer    _M_ptr() const noexcept {
return std::get<0>(_M_t); }
                         │      |                                              
                    ^
                         │      |                                              
                    |
                         │      |                                              
                    (7) ...to here
                         │
    <────────────────────┘
    │
  ‘int test(int)’: event 8
    │
    │../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C:14:8:
    │   14 |   a->x = 12; // { dg-warning "dereference of NULL" "" { xfail
*-*-*} }
    │      |   ~~~~~^~~~
    │      |        |
    │      |        (8) ⚠️  dereference of NULL ‘a.std::unique_ptr<A,
std::default_delete<A> >::_M_t.std::__uniq_ptr_data<A, std::default_delete<A>,
true, true>::<unnamed>.std::__uniq_ptr_impl<A, std::default_delete<A>
>::_M_t.std::tuple<A*, std::default_delete<A> >::<unnamed>.std::_Tuple_impl<0,
A*, std::default_delete<A> >::<unnamed>.std::_Head_base<0, A*,
false>::_M_head_impl’
    │

where _M_head_impl is an implementation detail of std::unique_ptr and that
typename at (8) is indecipherably long.

A C++ developer presumably thinks of this as a std::unique_ptr "being nullptr",
so presumably we should emit something like:

../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C: In function ‘int
test(int)’:
../../src/gcc/testsuite/g++.dg/analyzer/std-unique_ptr-2.C:14:8: warning:
dereference of NULL ‘a’ [CWE-476] [-Wanalyzer-null-dereference]
   14 |   a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
      |   ~~~~~^~~~
  ‘int test(int)’: event 1
   12 |   if (flag)
      |   ^~
      |   |
      |   (1) following ‘false’ branch (when ‘flag == 0’)... ─>─┐
      |                                                         │
  ‘int test(int)’: events 2-3
      |                                                         │
      |┌────────────────────────────────────────────────────────┘
   12 |│  if (flag)
      |│  ^~
      |│  |
      |└─>(2) ...to here
   13 |     a = make_ptr ();
   14 |   a->x = 12; // { dg-warning "dereference of NULL" "" { xfail *-*-*} }
      |   ~~~~~~~~~
      |        |
      |        (3) ⚠️  dereference of NULL ‘a’


We already have a way to decode the implementation details of various std::
types in terms the user understands: gdb pretty-printers.

(Possibly) crazy idea: have some way to run those pretty-printers on the
analyzer's model of the simulated state of memory along the given execution
path.

But maybe that doesn't give us the information we want.

Possible interaction here with bug 106386, and that we should instead complain
when preconditions don't hold.


Referenced Bugs:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97110
[Bug 97110] [meta-bug] tracker bug for supporting C++ in -fanalyzer

Reply via email to