]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: fix false +ves when testing <stdbool.h>/_Bool values [PR124451]
authorDavid Malcolm <dmalcolm@redhat.com>
Thu, 12 Mar 2026 00:45:29 +0000 (20:45 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Thu, 12 Mar 2026 00:45:29 +0000 (20:45 -0400)
PR analyzer/124451 reports false positives from -Wanalyzer-malloc-leak
when using inverted "bool" values to track whether an allocation
happened.

The root cause is that with _Bool, the inversion is a bitwise not,
and this leads to the test of the condition against 0 to not be
recognized by malloc_state_machine::on_condition.  Hence on_condition
fails to transition the state machine for the pointer from "unchecked"
to "null" or "nonnull", leaving it in the "unchecked" state.

This patch generalizes the folding logic in the analyzer that folds
  "!(x CMP y)" => "x !CMP y"
so that it also folds
  "~(x CMP y)" => "x !CMP y"
when the bitwise not is on a boolean type, fixing the false positives.

gcc/analyzer/ChangeLog:
PR analyzer/124451
* region-model-manager.cc
(region_model_manager::maybe_invert_comparison_in_unaryop): New,
adapted from...
(region_model_manager::maybe_fold_unaryop): ...here, splitting out
the comparison inversion case for TRUTH_NOT_EXPR.  Add a case for
BIT_NOT_EXPR, reusing the inversion logic when dealing with
boolean types.
* region-model-manager.h
(region_model_manager::maybe_invert_comparison_in_unaryop): New decl.

gcc/testsuite/ChangeLog:
PR analyzer/124451
* gcc.dg/analyzer/conditionals-pr124451-_Bool.c: New test.
* gcc.dg/analyzer/conditionals-pr124451-enum.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/analyzer/region-model-manager.cc
gcc/analyzer/region-model-manager.h
gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-_Bool.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-enum.c [new file with mode: 0644]

index 702a8ae72440aa4e85df15e48a43daee4c2ead2e..c4f91ecd6884edace0fb6e4a451c0808fcfbe0a1 100644 (file)
@@ -405,6 +405,31 @@ region_model_manager::get_ptr_svalue (tree ptr_type, const region *pointee)
   return sval;
 }
 
+/* Subroutine of region_model_manager::maybe_fold_unaryop
+   when the arg is a binop_svalue.
+   Invert comparisons e.g. "!(x == y)" => "x != y".
+   Otherwise, return nullptr.  */
+
+const svalue *
+region_model_manager::
+maybe_invert_comparison_in_unaryop (tree result_type,
+                                   const binop_svalue *binop)
+{
+  if (TREE_CODE_CLASS (binop->get_op ()) == tcc_comparison)
+    {
+      enum tree_code inv_op
+       = invert_tree_comparison (binop->get_op (),
+                                 HONOR_NANS (binop->get_type ()));
+      if (inv_op != ERROR_MARK)
+       return get_or_create_cast
+         (result_type,
+          get_or_create_binop (binop->get_type (), inv_op,
+                               binop->get_arg0 (),
+                               binop->get_arg1 ()));
+    }
+  return nullptr;
+}
+
 /* Subroutine of region_model_manager::get_or_create_unaryop.
    Attempt to fold the inputs and return a simpler svalue *.
    Otherwise, return nullptr.  */
@@ -470,16 +495,9 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op,
       {
        /* Invert comparisons e.g. "!(x == y)" => "x != y".  */
        if (const binop_svalue *binop = arg->dyn_cast_binop_svalue ())
-         if (TREE_CODE_CLASS (binop->get_op ()) == tcc_comparison)
-           {
-             enum tree_code inv_op
-               = invert_tree_comparison (binop->get_op (),
-                                         HONOR_NANS (binop->get_type ()));
-             if (inv_op != ERROR_MARK)
-               return get_or_create_binop (binop->get_type (), inv_op,
-                                           binop->get_arg0 (),
-                                           binop->get_arg1 ());
-           }
+         if (const svalue *folded
+               = maybe_invert_comparison_in_unaryop (type, binop))
+           return folded;
       }
       break;
     case NEGATE_EXPR:
@@ -493,6 +511,19 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op,
            return unaryop->get_arg ();
       }
       break;
+    case BIT_NOT_EXPR:
+      {
+       /* Invert comparisons for e.g. "~(x == y)" => "x != y".  */
+       if (type
+           && TREE_CODE (type) == BOOLEAN_TYPE
+           && arg->get_type ()
+           && TREE_CODE (arg->get_type ()) == BOOLEAN_TYPE)
+         if (const binop_svalue *binop = arg->dyn_cast_binop_svalue ())
+           if (const svalue *folded
+               = maybe_invert_comparison_in_unaryop (type, binop))
+             return folded;
+      }
+      break;
     }
 
   /* Constants.  */
index 5399a0fc3b5eacb1b51dd392b16901e57ddd31a3..44122ec05f61621beb301917b8d3f5e2ef4891b8 100644 (file)
@@ -181,6 +181,9 @@ private:
   bool too_complex_p (const complexity &c) const;
   bool reject_if_too_complex (svalue *sval);
 
+  const svalue *
+  maybe_invert_comparison_in_unaryop (tree type,
+                                     const binop_svalue *binop);
   const svalue *maybe_fold_unaryop (tree type, enum tree_code op,
                                    const svalue *arg);
   const svalue *maybe_fold_sub_svalue (tree type,
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-_Bool.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-_Bool.c
new file mode 100644 (file)
index 0000000..0eeee77
--- /dev/null
@@ -0,0 +1,34 @@
+typedef _Bool BOOL;
+#define NULL ((void *)0)
+
+int test_eq_null_inverted ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (!p_is_null) __builtin_free(p);
+    return 0;
+}
+
+int test_eq_null_compared_against_false ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (p_is_null == false) __builtin_free(p);
+    return 0;
+}
+
+int test_ne_null ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_notnull = (p != NULL);
+    if (p_is_notnull) __builtin_free(p);
+    return 0;
+}
+
+int test_eq_null ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (p_is_null) {} else __builtin_free(p);
+    return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-enum.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-enum.c
new file mode 100644 (file)
index 0000000..18e9c85
--- /dev/null
@@ -0,0 +1,36 @@
+/* { dg-additional-options "-std=c99" } */
+
+typedef enum { false = 0, true } BOOL;
+#define NULL ((void *)0)
+
+int test_eq_null_inverted ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (!p_is_null) __builtin_free(p);
+    return 0;
+}
+
+int test_eq_null_compared_against_false ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (p_is_null == false) __builtin_free(p);
+    return 0;
+}
+
+int test_ne_null ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_notnull = (p != NULL);
+    if (p_is_notnull) __builtin_free(p);
+    return 0;
+}
+
+int test_eq_null ()
+{
+    char *p = __builtin_malloc(1);
+    const BOOL p_is_null = (p == NULL);
+    if (p_is_null) {} else __builtin_free(p);
+    return 0;
+}