Bug 104955 - Analyzer slowdown with many diagnostics
Summary: Analyzer slowdown with many diagnostics
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: analyzer (show other bugs)
Version: 12.0
: P3 normal
Target Milestone: ---
Assignee: David Malcolm
URL:
Keywords:
Depends on:
Blocks: 104954
  Show dependency treegraph
 
Reported: 2022-03-16 13:51 UTC by David Malcolm
Modified: 2022-11-03 04:46 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description David Malcolm 2022-03-16 13:51:14 UTC
The following artificial testcase for -fanalyzer seems to take at least several minutes; perhaps much more:

#define DOUBLE_FREE()				\
  do {						\
    void *p = __builtin_malloc (1024);		\
    __builtin_free (p);				\
    __builtin_free (p);				\
  } while (0)

#define DOUBLE_FREE_x_10()			\
  do {						\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
    DOUBLE_FREE();				\
  } while (0)

#define DOUBLE_FREE_x_100()			\
  do {						\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
    DOUBLE_FREE_x_10();				\
  } while (0)

#define DOUBLE_FREE_x_1000()			\
  do {						\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
    DOUBLE_FREE_x_100();			\
  } while (0)

void test_1 (void)
{
  DOUBLE_FREE_x_1000 (); 
}

Breaking into it shows that it seems to be spending the bulk of its time exploring paths to determine if they are feasible (despite the fact that there's no control flow at all):

(gdb) bt
#0  0x0000000000f22750 in hash_table<hash_map<ana::region const*, ana::binding_cluster*, simple_hashmap_traits<default_hash_traits<ana::region const*>, ana::binding_cluster*> >::hash_entry, false, xcallocator>::find_slot_with_hash (this=this@entry=0x7fffffffc768, 
    comparable=@0x7fffffffc5f8: 0x292b5c0, hash=5396152, insert=insert@entry=INSERT) at ../../src/gcc/hash-traits.h:186
#1  0x0000000000f1b976 in hash_map<ana::region const*, ana::binding_cluster*, simple_hashmap_traits<default_hash_traits<ana::region const*>, ana::binding_cluster*> >::put (v=<optimized out>, k=@0x7fffffffc5f8: 0x292b5c0, this=0x7fffffffc768) at ../../src/gcc/hash-traits.h:162
#2  ana::store::store (this=this@entry=0x7fffffffc768, other=...) at ../../src/gcc/analyzer/store.cc:2046
#3  0x0000000000eeaecf in ana::region_model::region_model (this=this@entry=0x7fffffffc760, other=...) at ../../src/gcc/analyzer/region-model.cc:260
#4  0x0000000000eccf71 in ana::feasibility_state::feasibility_state (this=0x7fffffffc760, other=...) at ../../src/gcc/analyzer/engine.cc:4478
#5  0x00000000018a51f0 in ana::epath_finder::process_worklist_item (this=<optimized out>, worklist=0x7fffffffc950, tg=..., fg=0x7fffffffc8a0, 
    target_enode=0x2bdcf60, diag_idx=305, out_best_path=0x7fffffffc858) at ../../src/gcc/analyzer/feasible-graph.h:96
#6  0x00000000018a603c in ana::epath_finder::explore_feasible_paths (this=0x7fffffffcb90, target_enode=0x2bdcf60, desc=0x1a64f09 "double_free", 
    diag_idx=305) at ../../src/gcc/analyzer/diagnostic-manager.cc:414
#7  0x00000000018a6787 in ana::saved_diagnostic::calc_best_epath (this=0x2bddbf0, pf=0x7fffffffcb90)
    at ../../src/gcc/analyzer/diagnostic-manager.cc:736
#8  0x00000000018aaece in ana::dedupe_winners::add (this=this@entry=0x7fffffffcba0, logger=0x0, pf=pf@entry=0x7fffffffcb90, sd=0x2bddbf0)
    at ../../src/gcc/analyzer/diagnostic-manager.cc:1065
#9  0x00000000018a7ece in ana::diagnostic_manager::emit_saved_diagnostics (this=0x7fffffffcea0, eg=...)
    at ../../src/gcc/analyzer/analyzer-logging.h:150
#10 0x0000000000ed79ab in ana::impl_run_checkers (logger=logger@entry=0x0) at ../../src/gcc/analyzer/exploded-graph.h:857
#11 0x0000000000ed8804 in ana::run_checkers () at ../../src/gcc/analyzer/analyzer-logging.h:150
Comment 1 David Malcolm 2022-03-16 13:57:49 UTC
Also takes a long time with -Wno-analyzer-double-free; perhaps we ought to reject saved_diagnostics that will ultimately not be emitted.
Comment 2 David Malcolm 2022-03-16 13:59:44 UTC
I suspect that this issue is due to building a feasible_graph per saved diagnostic, thus leading to an O(N^2) where as the function gets bigger, each individual diagnostic requires more work.  Perhaps fixable by amortizing the work, by sharing one feasible_graph for all diagnostics in the diagnostic_manager.
Comment 3 CVS Commits 2022-03-16 18:04:56 UTC
The master branch has been updated by David Malcolm <dmalcolm@gcc.gnu.org>:

https://gcc.gnu.org/g:7fd6e36ea9aa8575841ff1da08b4aebc0298abe2

commit r12-7677-g7fd6e36ea9aa8575841ff1da08b4aebc0298abe2
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Wed Mar 16 10:54:44 2022 -0400

    analyzer: early rejection of disabled warnings [PR104955]
    
    Avoid generating execution paths for warnings that are ultimately
    rejected due to -Wno-analyzer-* flags.
    
    This improves the test case from taking at least several minutes
    (before I killed it) to taking under a second.
    
    This doesn't fix the slowdown seen in PR analyzer/104955 with large
    numbers of warnings when the warnings are still enabled.
    
    gcc/analyzer/ChangeLog:
            PR analyzer/104955
            * diagnostic-manager.cc (get_emission_location): New.
            (diagnostic_manager::diagnostic_manager): Initialize
            m_num_disabled_diagnostics.
            (diagnostic_manager::add_diagnostic): Reject diagnostics that
            will eventually be rejected due to being disabled.
            (diagnostic_manager::emit_saved_diagnostics): Log the number
            of disabled diagnostics.
            (diagnostic_manager::emit_saved_diagnostic): Split out logic for
            determining emission location to get_emission_location.
            * diagnostic-manager.h
            (diagnostic_manager::m_num_disabled_diagnostics): New field.
            * engine.cc (stale_jmp_buf::get_controlling_option): New.
            (stale_jmp_buf::emit): Use it.
            * pending-diagnostic.h
            (pending_diagnostic::get_controlling_option): New vfunc.
            * region-model.cc
            (poisoned_value_diagnostic::get_controlling_option): New.
            (poisoned_value_diagnostic::emit): Use it.
            (shift_count_negative_diagnostic::get_controlling_option): New.
            (shift_count_negative_diagnostic::emit): Use it.
            (shift_count_overflow_diagnostic::get_controlling_option): New.
            (shift_count_overflow_diagnostic::emit): Use it.
            (dump_path_diagnostic::get_controlling_option): New.
            (dump_path_diagnostic::emit): Use it.
            (write_to_const_diagnostic::get_controlling_option): New.
            (write_to_const_diagnostic::emit): Use it.
            (write_to_string_literal_diagnostic::get_controlling_option): New.
            (write_to_string_literal_diagnostic::emit): Use it.
            * sm-file.cc (double_fclose::get_controlling_option): New.
            (double_fclose::emit): Use it.
            (file_leak::get_controlling_option): New.
            (file_leak::emit): Use it.
            * sm-malloc.cc (mismatching_deallocation::get_controlling_option):
            New.
            (mismatching_deallocation::emit): Use it.
            (double_free::get_controlling_option): New.
            (double_free::emit): Use it.
            (possible_null_deref::get_controlling_option): New.
            (possible_null_deref::emit): Use it.
            (possible_null_arg::get_controlling_option): New.
            (possible_null_arg::emit): Use it.
            (null_deref::get_controlling_option): New.
            (null_deref::emit): Use it.
            (null_arg::get_controlling_option): New.
            (null_arg::emit): Use it.
            (use_after_free::get_controlling_option): New.
            (use_after_free::emit): Use it.
            (malloc_leak::get_controlling_option): New.
            (malloc_leak::emit): Use it.
            (free_of_non_heap::get_controlling_option): New.
            (free_of_non_heap::emit): Use it.
            * sm-pattern-test.cc (pattern_match::get_controlling_option): New.
            (pattern_match::emit): Use it.
            * sm-sensitive.cc
            (exposure_through_output_file::get_controlling_option): New.
            (exposure_through_output_file::emit): Use it.
            * sm-signal.cc (signal_unsafe_call::get_controlling_option): New.
            (signal_unsafe_call::emit): Use it.
            * sm-taint.cc (tainted_array_index::get_controlling_option): New.
            (tainted_array_index::emit): Use it.
            (tainted_offset::get_controlling_option): New.
            (tainted_offset::emit): Use it.
            (tainted_size::get_controlling_option): New.
            (tainted_size::emit): Use it.
            (tainted_divisor::get_controlling_option): New.
            (tainted_divisor::emit): Use it.
            (tainted_allocation_size::get_controlling_option): New.
            (tainted_allocation_size::emit): Use it.
    
    gcc/testsuite/ChangeLog:
            * gcc.dg/analyzer/many-disabled-diagnostics.c: New test.
            * gcc.dg/plugin/analyzer_gil_plugin.c
            (gil_diagnostic::get_controlling_option): New.
            (double_save_thread::emit): Use it.
            (fncall_without_gil::emit): Likewise.
            (pyobject_usage_without_gil::emit): Likewise.
    
    Signed-off-by: David Malcolm <dmalcolm@redhat.com>