On Fri, Feb 14, 2025 at 09:14:48AM -0500, David Malcolm wrote:
> I haven't used ranger yet within the analyzer, and I wonder if there is
> a philosophical divide here between the goals of optimization versus
> bug finding: the optimizer makes use of undefined behavior in order to
> add assumptions about the state of the code ("X can't happen"), whereas
> the analyzer ought to report on places where undefined behavior could
> happen ("if X happened here, it would be bad, let's warn the user").

You're right, that is why also using the ranger in the __builtin_*object_size
handling and sanitizers is problematic.
Maybe we'll need a ranger mode which only looks at clearly guaranteed things
rather than exploiting undefined behavior (including whatever is saved in
SSA_NAME_RANGE_INFO).

> One way of expressing the conditional nature of this property in the
> analyzer would be to "bifurcate" the analysis so that there are two
> out-edges in the exploded graph, covering the ptr == NULL vs ptr !=
> NULL conditions explicitly and separately.  But that's fiddly, alas,
> and not something to be doing for GCC 15 at this stage (sorry).

Indeed.

> I see that the code from 
>    state_t state = sm_ctxt.get_state (stmt, arg);
> onwards seems to be a copy-and-paste of the existing code from within
> the
>    /* Handle "__attribute__((nonnull))".   */
> suite.  malloc_state_machine::on_stmt is already longer than I'm
> comfortable with, so please can you introduce a subroutine to avoid the
> copy-and-paste (probably a private member function of
> malloc_state_machine).
> 
> OK for trunk with that change (assuming usual testing, of course)

Here is what I've committed after another bootstrap/regtest on
x86_64-linux and i686-linux.

2025-02-24  Jakub Jelinek  <ja...@redhat.com>

        PR c/117023
gcc/analyzer/
        * sm-malloc.cc (malloc_state_machine::handle_nonnull): New private
        method.
        (malloc_state_machine::on_stmt): Use it for nonnull attribute arguments.
        Handle also nonnull_if_nonzero attributes.
gcc/testsuite/
        * c-c++-common/analyzer/call-summaries-malloc.c
        (test_use_without_check): Pass 4 rather than sz to memset.
        * c-c++-common/analyzer/strncpy-1.c (test_null_dst,
        test_null_src): Pass 42 rather than count to strncpy.

--- gcc/analyzer/sm-malloc.cc.jj        2024-10-24 22:56:14.111158433 +0200
+++ gcc/analyzer/sm-malloc.cc   2025-02-24 00:28:51.111155033 +0100
@@ -501,6 +501,12 @@ private:
   void on_zero_assignment (sm_context &sm_ctxt,
                           const gimple *stmt,
                           tree lhs) const;
+  void handle_nonnull (sm_context &sm_ctx,
+                      const supernode *node,
+                      const gimple *stmt,
+                      tree fndecl,
+                      tree arg,
+                      unsigned i) const;
 
   /* A map for consolidating deallocators so that they are
      unique per deallocator FUNCTION_DECL.  */
@@ -2004,6 +2010,43 @@ malloc_state_machine::maybe_assume_non_n
     }
 }
 
+/* Helper method for malloc_state_machine::on_stmt.  Handle a single
+   argument (Ith argument ARG) if it is nonnull or nonnull_if_nonzero
+   and size is nonzero.  */
+
+void
+malloc_state_machine::handle_nonnull (sm_context &sm_ctxt,
+                                     const supernode *node,
+                                     const gimple *stmt,
+                                     tree fndecl,
+                                     tree arg,
+                                     unsigned i) const
+{
+  state_t state = sm_ctxt.get_state (stmt, arg);
+  /* Can't use a switch as the states are non-const.  */
+  /* Do use the fndecl that caused the warning so that the
+     misused attributes are printed and the user not confused.  */
+  if (unchecked_p (state))
+    {
+      tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
+      sm_ctxt.warn (node, stmt, arg,
+                   make_unique<possible_null_arg> (*this, diag_arg, fndecl,
+                                                   i));
+      const allocation_state *astate
+       = as_a_allocation_state (state);
+      sm_ctxt.set_next_state (stmt, arg, astate->get_nonnull ());
+    }
+  else if (state == m_null)
+    {
+      tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
+      sm_ctxt.warn (node, stmt, arg,
+                   make_unique<null_arg> (*this, diag_arg, fndecl, i));
+      sm_ctxt.set_next_state (stmt, arg, m_stop);
+    }
+  else if (state == m_start)
+    maybe_assume_non_null (sm_ctxt, arg, stmt);
+}
+
 /* Implementation of state_machine::on_stmt vfunc for malloc_state_machine.  */
 
 bool
@@ -2118,37 +2161,37 @@ malloc_state_machine::on_stmt (sm_contex
                       just the specified pointers.  */
                    if (bitmap_empty_p (nonnull_args)
                        || bitmap_bit_p (nonnull_args, i))
-                     {
-                       state_t state = sm_ctxt.get_state (stmt, arg);
-                       /* Can't use a switch as the states are non-const.  */
-                       /* Do use the fndecl that caused the warning so that the
-                          misused attributes are printed and the user not
-                          confused.  */
-                       if (unchecked_p (state))
-                         {
-                           tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
-                           sm_ctxt.warn (node, stmt, arg,
-                                         make_unique<possible_null_arg>
-                                           (*this, diag_arg, fndecl, i));
-                           const allocation_state *astate
-                             = as_a_allocation_state (state);
-                           sm_ctxt.set_next_state (stmt, arg,
-                                                   astate->get_nonnull ());
-                         }
-                       else if (state == m_null)
-                         {
-                           tree diag_arg = sm_ctxt.get_diagnostic_tree (arg);
-                           sm_ctxt.warn (node, stmt, arg,
-                                         make_unique<null_arg>
-                                           (*this, diag_arg, fndecl, i));
-                           sm_ctxt.set_next_state (stmt, arg, m_stop);
-                         }
-                       else if (state == m_start)
-                         maybe_assume_non_null (sm_ctxt, arg, stmt);
-                     }
+                     handle_nonnull (sm_ctxt, node, stmt, fndecl, arg, i);
                  }
                BITMAP_FREE (nonnull_args);
              }
+           /* Handle __attribute__((nonnull_if_nonzero (x, y))).  */
+           if (fntype)
+             for (tree attrs = TYPE_ATTRIBUTES (fntype);
+                  (attrs = lookup_attribute ("nonnull_if_nonzero", attrs));
+                  attrs = TREE_CHAIN (attrs))
+               {
+                 tree args = TREE_VALUE (attrs);
+                 unsigned int idx = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+                 unsigned int idx2
+                   = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+                 if (idx < gimple_call_num_args (stmt)
+                     && idx2 < gimple_call_num_args (stmt))
+                   {
+                     tree arg = gimple_call_arg (stmt, idx);
+                     tree arg2 = gimple_call_arg (stmt, idx2);
+                     if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE
+                         || !INTEGRAL_TYPE_P (TREE_TYPE (arg2))
+                         || integer_zerop (arg2))
+                       continue;
+                     if (integer_nonzerop (arg2))
+                       ;
+                     else
+                       /* FIXME: Use ranger here to query arg2 range?  */
+                       continue;
+                     handle_nonnull (sm_ctxt, node, stmt, fndecl, arg, idx);
+                   }
+               }
          }
 
          /* Check for this after nonnull, so that if we have both
--- gcc/testsuite/c-c++-common/analyzer/call-summaries-malloc.c.jj      
2024-09-18 15:03:34.578092119 +0200
+++ gcc/testsuite/c-c++-common/analyzer/call-summaries-malloc.c 2024-11-14 
10:54:35.445125885 +0100
@@ -67,7 +67,7 @@ void test_use_after_free (void)
 void test_use_without_check (size_t sz)
 {
   char *buf = (char *) wrapped_malloc (sz); /* { dg-message "this call could 
return NULL" } */
-  memset (buf, 'x', sz); /* { dg-warning "use of possibly-NULL 'buf' where 
non-null expected" } */
+  memset (buf, 'x', 4); /* { dg-warning "use of possibly-NULL 'buf' where 
non-null expected" } */
   wrapped_free (buf);
 }
 
--- gcc/testsuite/c-c++-common/analyzer/strncpy-1.c.jj  2024-01-18 
22:04:50.983005923 +0100
+++ gcc/testsuite/c-c++-common/analyzer/strncpy-1.c     2024-11-14 
10:55:26.035410085 +0100
@@ -20,13 +20,13 @@ test_passthrough (char *dst, const char
 char *
 test_null_dst (const char *src, size_t count)
 {
-  return strncpy (NULL, src, count); /* { dg-warning "use of NULL where 
non-null expected" } */
+  return strncpy (NULL, src, 42); /* { dg-warning "use of NULL where non-null 
expected" } */
 }
 
 char *
 test_null_src (char *dst, size_t count)
 {
-  return strncpy (dst, NULL, count); /* { dg-warning "use of NULL where 
non-null expected" } */
+  return strncpy (dst, NULL, 42); /* { dg-warning "use of NULL where non-null 
expected" } */
 }
 
 void

Reply via email to