From: David Malcolm Date: Thu, 12 Mar 2026 00:45:29 +0000 (-0400) Subject: analyzer: fix false +ves when testing /_Bool values [PR124451] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a1ff23e3727cce21833171dfecdd46edabc76e55;p=thirdparty%2Fgcc.git analyzer: fix false +ves when testing /_Bool values [PR124451] 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 --- diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 702a8ae7244..c4f91ecd688 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -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. */ diff --git a/gcc/analyzer/region-model-manager.h b/gcc/analyzer/region-model-manager.h index 5399a0fc3b5..44122ec05f6 100644 --- a/gcc/analyzer/region-model-manager.h +++ b/gcc/analyzer/region-model-manager.h @@ -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 index 00000000000..0eeee77099a --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-_Bool.c @@ -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 index 00000000000..18e9c85380e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-pr124451-enum.c @@ -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; +}