[gcc(refs/users/marxin/heads/marxin-gcc-benchmark-branch)] Initial commit of analyzer
Martin Liska
marxin@gcc.gnu.org
Mon Mar 30 10:05:16 GMT 2020
https://gcc.gnu.org/g:757bf1dff5e8cee34c0a75d06140ca972bfecfa7
commit 757bf1dff5e8cee34c0a75d06140ca972bfecfa7
Author: David Malcolm <dmalcolm@redhat.com>
Date: Fri Sep 27 09:23:16 2019 -0400
Initial commit of analyzer
This patch adds a static analysis pass to the middle-end, focusing
for this release on C code, and malloc/free issues in particular.
See:
https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer
gcc/ChangeLog:
* Makefile.in (lang_opt_files): Add analyzer.opt.
(ANALYZER_OBJS): New.
(OBJS): Add digraph.o, graphviz.o, ordered-hash-map-tests.o,
tristate.o and ANALYZER_OBJS.
(TEXI_GCCINT_FILES): Add analyzer.texi.
* common.opt (-fanalyzer): New driver option.
* config.in: Regenerate.
* configure: Regenerate.
* configure.ac (--disable-analyzer, ENABLE_ANALYZER): New option.
(gccdepdir): Also create depdir for "analyzer" subdir.
* digraph.cc: New file.
* digraph.h: New file.
* doc/analyzer.texi: New file.
* doc/gccint.texi ("Static Analyzer") New menu item.
(analyzer.texi): Include it.
* doc/invoke.texi ("Static Analyzer Options"): New list and new section.
("Warning Options"): Add static analysis warnings to the list.
(-Wno-analyzer-double-fclose): New option.
(-Wno-analyzer-double-free): New option.
(-Wno-analyzer-exposure-through-output-file): New option.
(-Wno-analyzer-file-leak): New option.
(-Wno-analyzer-free-of-non-heap): New option.
(-Wno-analyzer-malloc-leak): New option.
(-Wno-analyzer-possible-null-argument): New option.
(-Wno-analyzer-possible-null-dereference): New option.
(-Wno-analyzer-null-argument): New option.
(-Wno-analyzer-null-dereference): New option.
(-Wno-analyzer-stale-setjmp-buffer): New option.
(-Wno-analyzer-tainted-array-index): New option.
(-Wno-analyzer-use-after-free): New option.
(-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
(-Wno-analyzer-use-of-uninitialized-value): New option.
(-Wanalyzer-too-complex): New option.
(-fanalyzer-call-summaries): New warning.
(-fanalyzer-checker=): New warning.
(-fanalyzer-fine-grained): New warning.
(-fno-analyzer-state-merge): New warning.
(-fno-analyzer-state-purge): New warning.
(-fanalyzer-transitivity): New warning.
(-fanalyzer-verbose-edges): New warning.
(-fanalyzer-verbose-state-changes): New warning.
(-fanalyzer-verbosity=): New warning.
(-fdump-analyzer): New warning.
(-fdump-analyzer-callgraph): New warning.
(-fdump-analyzer-exploded-graph): New warning.
(-fdump-analyzer-exploded-nodes): New warning.
(-fdump-analyzer-exploded-nodes-2): New warning.
(-fdump-analyzer-exploded-nodes-3): New warning.
(-fdump-analyzer-supergraph): New warning.
* doc/sourcebuild.texi (dg-require-dot): New.
(dg-check-dot): New.
* gdbinit.in (break-on-saved-diagnostic): New command.
* graphviz.cc: New file.
* graphviz.h: New file.
* ordered-hash-map-tests.cc: New file.
* ordered-hash-map.h: New file.
* passes.def (pass_analyzer): Add before
pass_ipa_whole_program_visibility.
* selftest-run-tests.c (selftest::run_tests): Call
selftest::ordered_hash_map_tests_cc_tests.
* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
decl.
* shortest-paths.h: New file.
* timevar.def (TV_ANALYZER): New timevar.
(TV_ANALYZER_SUPERGRAPH): Likewise.
(TV_ANALYZER_STATE_PURGE): Likewise.
(TV_ANALYZER_PLAN): Likewise.
(TV_ANALYZER_SCC): Likewise.
(TV_ANALYZER_WORKLIST): Likewise.
(TV_ANALYZER_DUMP): Likewise.
(TV_ANALYZER_DIAGNOSTICS): Likewise.
(TV_ANALYZER_SHORTEST_PATHS): Likewise.
* tree-pass.h (make_pass_analyzer): New decl.
* tristate.cc: New file.
* tristate.h: New file.
gcc/analyzer/ChangeLog:
* ChangeLog: New file.
* analyzer-selftests.cc: New file.
* analyzer-selftests.h: New file.
* analyzer.opt: New file.
* analysis-plan.cc: New file.
* analysis-plan.h: New file.
* analyzer-logging.cc: New file.
* analyzer-logging.h: New file.
* analyzer-pass.cc: New file.
* analyzer.cc: New file.
* analyzer.h: New file.
* call-string.cc: New file.
* call-string.h: New file.
* checker-path.cc: New file.
* checker-path.h: New file.
* constraint-manager.cc: New file.
* constraint-manager.h: New file.
* diagnostic-manager.cc: New file.
* diagnostic-manager.h: New file.
* engine.cc: New file.
* engine.h: New file.
* exploded-graph.h: New file.
* pending-diagnostic.cc: New file.
* pending-diagnostic.h: New file.
* program-point.cc: New file.
* program-point.h: New file.
* program-state.cc: New file.
* program-state.h: New file.
* region-model.cc: New file.
* region-model.h: New file.
* sm-file.cc: New file.
* sm-malloc.cc: New file.
* sm-malloc.dot: New file.
* sm-pattern-test.cc: New file.
* sm-sensitive.cc: New file.
* sm-signal.cc: New file.
* sm-taint.cc: New file.
* sm.cc: New file.
* sm.h: New file.
* state-purge.cc: New file.
* state-purge.h: New file.
* supergraph.cc: New file.
* supergraph.h: New file.
gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/CVE-2005-1689-minimal.c: New test.
* gcc.dg/analyzer/abort.c: New test.
* gcc.dg/analyzer/alloca-leak.c: New test.
* gcc.dg/analyzer/analyzer-decls.h: New header.
* gcc.dg/analyzer/analyzer-verbosity-0.c: New test.
* gcc.dg/analyzer/analyzer-verbosity-1.c: New test.
* gcc.dg/analyzer/analyzer-verbosity-2.c: New test.
* gcc.dg/analyzer/analyzer.exp: New suite.
* gcc.dg/analyzer/attribute-nonnull.c: New test.
* gcc.dg/analyzer/call-summaries-1.c: New test.
* gcc.dg/analyzer/conditionals-2.c: New test.
* gcc.dg/analyzer/conditionals-3.c: New test.
* gcc.dg/analyzer/conditionals-notrans.c: New test.
* gcc.dg/analyzer/conditionals-trans.c: New test.
* gcc.dg/analyzer/data-model-1.c: New test.
* gcc.dg/analyzer/data-model-2.c: New test.
* gcc.dg/analyzer/data-model-3.c: New test.
* gcc.dg/analyzer/data-model-4.c: New test.
* gcc.dg/analyzer/data-model-5.c: New test.
* gcc.dg/analyzer/data-model-5b.c: New test.
* gcc.dg/analyzer/data-model-5c.c: New test.
* gcc.dg/analyzer/data-model-5d.c: New test.
* gcc.dg/analyzer/data-model-6.c: New test.
* gcc.dg/analyzer/data-model-7.c: New test.
* gcc.dg/analyzer/data-model-8.c: New test.
* gcc.dg/analyzer/data-model-9.c: New test.
* gcc.dg/analyzer/data-model-11.c: New test.
* gcc.dg/analyzer/data-model-12.c: New test.
* gcc.dg/analyzer/data-model-13.c: New test.
* gcc.dg/analyzer/data-model-14.c: New test.
* gcc.dg/analyzer/data-model-15.c: New test.
* gcc.dg/analyzer/data-model-16.c: New test.
* gcc.dg/analyzer/data-model-17.c: New test.
* gcc.dg/analyzer/data-model-18.c: New test.
* gcc.dg/analyzer/data-model-19.c: New test.
* gcc.dg/analyzer/data-model-path-1.c: New test.
* gcc.dg/analyzer/disabling.c: New test.
* gcc.dg/analyzer/dot-output.c: New test.
* gcc.dg/analyzer/double-free-lto-1-a.c: New test.
* gcc.dg/analyzer/double-free-lto-1-b.c: New test.
* gcc.dg/analyzer/double-free-lto-1.h: New header.
* gcc.dg/analyzer/equivalence.c: New test.
* gcc.dg/analyzer/explode-1.c: New test.
* gcc.dg/analyzer/explode-2.c: New test.
* gcc.dg/analyzer/factorial.c: New test.
* gcc.dg/analyzer/fibonacci.c: New test.
* gcc.dg/analyzer/fields.c: New test.
* gcc.dg/analyzer/file-1.c: New test.
* gcc.dg/analyzer/file-2.c: New test.
* gcc.dg/analyzer/function-ptr-1.c: New test.
* gcc.dg/analyzer/function-ptr-2.c: New test.
* gcc.dg/analyzer/function-ptr-3.c: New test.
* gcc.dg/analyzer/gzio-2.c: New test.
* gcc.dg/analyzer/gzio-3.c: New test.
* gcc.dg/analyzer/gzio-3a.c: New test.
* gcc.dg/analyzer/gzio.c: New test.
* gcc.dg/analyzer/infinite-recursion.c: New test.
* gcc.dg/analyzer/loop-2.c: New test.
* gcc.dg/analyzer/loop-2a.c: New test.
* gcc.dg/analyzer/loop-3.c: New test.
* gcc.dg/analyzer/loop-4.c: New test.
* gcc.dg/analyzer/loop.c: New test.
* gcc.dg/analyzer/malloc-1.c: New test.
* gcc.dg/analyzer/malloc-2.c: New test.
* gcc.dg/analyzer/malloc-3.c: New test.
* gcc.dg/analyzer/malloc-callbacks.c: New test.
* gcc.dg/analyzer/malloc-dce.c: New test.
* gcc.dg/analyzer/malloc-dedupe-1.c: New test.
* gcc.dg/analyzer/malloc-ipa-1.c: New test.
* gcc.dg/analyzer/malloc-ipa-10.c: New test.
* gcc.dg/analyzer/malloc-ipa-11.c: New test.
* gcc.dg/analyzer/malloc-ipa-12.c: New test.
* gcc.dg/analyzer/malloc-ipa-13.c: New test.
* gcc.dg/analyzer/malloc-ipa-2.c: New test.
* gcc.dg/analyzer/malloc-ipa-3.c: New test.
* gcc.dg/analyzer/malloc-ipa-4.c: New test.
* gcc.dg/analyzer/malloc-ipa-5.c: New test.
* gcc.dg/analyzer/malloc-ipa-6.c: New test.
* gcc.dg/analyzer/malloc-ipa-7.c: New test.
* gcc.dg/analyzer/malloc-ipa-8-double-free.c: New test.
* gcc.dg/analyzer/malloc-ipa-8-lto-a.c: New test.
* gcc.dg/analyzer/malloc-ipa-8-lto-b.c: New test.
* gcc.dg/analyzer/malloc-ipa-8-lto-c.c: New test.
* gcc.dg/analyzer/malloc-ipa-8-lto.h: New test.
* gcc.dg/analyzer/malloc-ipa-8-unchecked.c: New test.
* gcc.dg/analyzer/malloc-ipa-9.c: New test.
* gcc.dg/analyzer/malloc-macro-inline-events.c: New test.
* gcc.dg/analyzer/malloc-macro-separate-events.c: New test.
* gcc.dg/analyzer/malloc-macro.h: New header.
* gcc.dg/analyzer/malloc-many-paths-1.c: New test.
* gcc.dg/analyzer/malloc-many-paths-2.c: New test.
* gcc.dg/analyzer/malloc-many-paths-3.c: New test.
* gcc.dg/analyzer/malloc-paths-1.c: New test.
* gcc.dg/analyzer/malloc-paths-10.c: New test.
* gcc.dg/analyzer/malloc-paths-2.c: New test.
* gcc.dg/analyzer/malloc-paths-3.c: New test.
* gcc.dg/analyzer/malloc-paths-4.c: New test.
* gcc.dg/analyzer/malloc-paths-5.c: New test.
* gcc.dg/analyzer/malloc-paths-6.c: New test.
* gcc.dg/analyzer/malloc-paths-7.c: New test.
* gcc.dg/analyzer/malloc-paths-8.c: New test.
* gcc.dg/analyzer/malloc-paths-9.c: New test.
* gcc.dg/analyzer/malloc-vs-local-1a.c: New test.
* gcc.dg/analyzer/malloc-vs-local-1b.c: New test.
* gcc.dg/analyzer/malloc-vs-local-2.c: New test.
* gcc.dg/analyzer/malloc-vs-local-3.c: New test.
* gcc.dg/analyzer/malloc-vs-local-4.c: New test.
* gcc.dg/analyzer/operations.c: New test.
* gcc.dg/analyzer/params-2.c: New test.
* gcc.dg/analyzer/params.c: New test.
* gcc.dg/analyzer/paths-1.c: New test.
* gcc.dg/analyzer/paths-1a.c: New test.
* gcc.dg/analyzer/paths-2.c: New test.
* gcc.dg/analyzer/paths-3.c: New test.
* gcc.dg/analyzer/paths-4.c: New test.
* gcc.dg/analyzer/paths-5.c: New test.
* gcc.dg/analyzer/paths-6.c: New test.
* gcc.dg/analyzer/paths-7.c: New test.
* gcc.dg/analyzer/pattern-test-1.c: New test.
* gcc.dg/analyzer/pattern-test-2.c: New test.
* gcc.dg/analyzer/pointer-merging.c: New test.
* gcc.dg/analyzer/pr61861.c: New test.
* gcc.dg/analyzer/pragma-1.c: New test.
* gcc.dg/analyzer/scope-1.c: New test.
* gcc.dg/analyzer/sensitive-1.c: New test.
* gcc.dg/analyzer/setjmp-1.c: New test.
* gcc.dg/analyzer/setjmp-2.c: New test.
* gcc.dg/analyzer/setjmp-3.c: New test.
* gcc.dg/analyzer/setjmp-4.c: New test.
* gcc.dg/analyzer/setjmp-5.c: New test.
* gcc.dg/analyzer/setjmp-6.c: New test.
* gcc.dg/analyzer/setjmp-7.c: New test.
* gcc.dg/analyzer/setjmp-7a.c: New test.
* gcc.dg/analyzer/setjmp-8.c: New test.
* gcc.dg/analyzer/setjmp-9.c: New test.
* gcc.dg/analyzer/signal-1.c: New test.
* gcc.dg/analyzer/signal-2.c: New test.
* gcc.dg/analyzer/signal-3.c: New test.
* gcc.dg/analyzer/signal-4a.c: New test.
* gcc.dg/analyzer/signal-4b.c: New test.
* gcc.dg/analyzer/strcmp-1.c: New test.
* gcc.dg/analyzer/switch.c: New test.
* gcc.dg/analyzer/taint-1.c: New test.
* gcc.dg/analyzer/zlib-1.c: New test.
* gcc.dg/analyzer/zlib-2.c: New test.
* gcc.dg/analyzer/zlib-3.c: New test.
* gcc.dg/analyzer/zlib-4.c: New test.
* gcc.dg/analyzer/zlib-5.c: New test.
* gcc.dg/analyzer/zlib-6.c: New test.
* lib/gcc-defs.exp (dg-check-dot): New procedure.
* lib/target-supports.exp (check_dot_available): New procedure.
(check_effective_target_analyzer): New.
* lib/target-supports-dg.exp (dg-require-dot): New procedure.
Diff:
---
gcc/ChangeLog | 78 +
gcc/Makefile.in | 35 +-
gcc/analyzer/ChangeLog | 56 +
gcc/analyzer/analysis-plan.cc | 130 +
gcc/analyzer/analysis-plan.h | 56 +
gcc/analyzer/analyzer-logging.cc | 224 +
gcc/analyzer/analyzer-logging.h | 260 +
gcc/analyzer/analyzer-pass.cc | 102 +
gcc/analyzer/analyzer-selftests.cc | 61 +
gcc/analyzer/analyzer-selftests.h | 44 +
gcc/analyzer/analyzer.cc | 151 +
gcc/analyzer/analyzer.h | 122 +
gcc/analyzer/analyzer.opt | 181 +
gcc/analyzer/call-string.cc | 233 +
gcc/analyzer/call-string.h | 76 +
gcc/analyzer/checker-path.cc | 957 +++
gcc/analyzer/checker-path.h | 586 ++
gcc/analyzer/constraint-manager.cc | 2260 ++++++
gcc/analyzer/constraint-manager.h | 246 +
gcc/analyzer/diagnostic-manager.cc | 1243 ++++
gcc/analyzer/diagnostic-manager.h | 135 +
gcc/analyzer/engine.cc | 3614 +++++++++
gcc/analyzer/engine.h | 26 +
gcc/analyzer/exploded-graph.h | 829 +++
gcc/analyzer/pending-diagnostic.cc | 70 +
gcc/analyzer/pending-diagnostic.h | 266 +
gcc/analyzer/program-point.cc | 554 ++
gcc/analyzer/program-point.h | 310 +
gcc/analyzer/program-state.cc | 1356 ++++
gcc/analyzer/program-state.h | 363 +
gcc/analyzer/region-model.cc | 7785 ++++++++++++++++++++
gcc/analyzer/region-model.h | 2057 ++++++
gcc/analyzer/sm-file.cc | 339 +
gcc/analyzer/sm-malloc.cc | 797 ++
gcc/analyzer/sm-malloc.dot | 89 +
gcc/analyzer/sm-pattern-test.cc | 152 +
gcc/analyzer/sm-sensitive.cc | 248 +
gcc/analyzer/sm-signal.cc | 328 +
gcc/analyzer/sm-taint.cc | 328 +
gcc/analyzer/sm.cc | 122 +
gcc/analyzer/sm.h | 178 +
gcc/analyzer/state-purge.cc | 534 ++
gcc/analyzer/state-purge.h | 162 +
gcc/analyzer/supergraph.cc | 961 +++
gcc/analyzer/supergraph.h | 554 ++
gcc/common.opt | 4 +
gcc/config.in | 6 +
gcc/configure | 25 +-
gcc/configure.ac | 14 +-
gcc/digraph.cc | 188 +
gcc/digraph.h | 246 +
gcc/doc/analyzer.texi | 513 ++
gcc/doc/gccint.texi | 2 +
gcc/doc/invoke.texi | 435 +-
gcc/doc/sourcebuild.texi | 6 +
gcc/gdbinit.in | 10 +
gcc/graphviz.cc | 100 +
gcc/graphviz.h | 53 +
gcc/ordered-hash-map-tests.cc | 247 +
gcc/ordered-hash-map.h | 188 +
gcc/passes.def | 1 +
gcc/selftest-run-tests.c | 7 +
gcc/selftest.h | 3 +
gcc/shortest-paths.h | 145 +
gcc/testsuite/ChangeLog | 156 +
.../gcc.dg/analyzer/CVE-2005-1689-minimal.c | 30 +
gcc/testsuite/gcc.dg/analyzer/abort.c | 72 +
gcc/testsuite/gcc.dg/analyzer/alloca-leak.c | 8 +
gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h | 36 +
.../gcc.dg/analyzer/analyzer-verbosity-0.c | 163 +
.../gcc.dg/analyzer/analyzer-verbosity-1.c | 191 +
.../gcc.dg/analyzer/analyzer-verbosity-2.c | 222 +
gcc/testsuite/gcc.dg/analyzer/analyzer.exp | 49 +
gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c | 81 +
gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c | 14 +
gcc/testsuite/gcc.dg/analyzer/conditionals-2.c | 45 +
gcc/testsuite/gcc.dg/analyzer/conditionals-3.c | 47 +
.../gcc.dg/analyzer/conditionals-notrans.c | 159 +
gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c | 144 +
gcc/testsuite/gcc.dg/analyzer/data-model-1.c | 1085 +++
gcc/testsuite/gcc.dg/analyzer/data-model-10.c | 17 +
gcc/testsuite/gcc.dg/analyzer/data-model-11.c | 6 +
gcc/testsuite/gcc.dg/analyzer/data-model-12.c | 13 +
gcc/testsuite/gcc.dg/analyzer/data-model-13.c | 21 +
gcc/testsuite/gcc.dg/analyzer/data-model-14.c | 24 +
gcc/testsuite/gcc.dg/analyzer/data-model-15.c | 34 +
gcc/testsuite/gcc.dg/analyzer/data-model-16.c | 52 +
gcc/testsuite/gcc.dg/analyzer/data-model-17.c | 20 +
gcc/testsuite/gcc.dg/analyzer/data-model-18.c | 22 +
gcc/testsuite/gcc.dg/analyzer/data-model-19.c | 31 +
gcc/testsuite/gcc.dg/analyzer/data-model-2.c | 13 +
gcc/testsuite/gcc.dg/analyzer/data-model-3.c | 15 +
gcc/testsuite/gcc.dg/analyzer/data-model-4.c | 16 +
gcc/testsuite/gcc.dg/analyzer/data-model-5.c | 100 +
gcc/testsuite/gcc.dg/analyzer/data-model-5b.c | 91 +
gcc/testsuite/gcc.dg/analyzer/data-model-5c.c | 84 +
gcc/testsuite/gcc.dg/analyzer/data-model-5d.c | 64 +
gcc/testsuite/gcc.dg/analyzer/data-model-6.c | 14 +
gcc/testsuite/gcc.dg/analyzer/data-model-7.c | 20 +
gcc/testsuite/gcc.dg/analyzer/data-model-8.c | 26 +
gcc/testsuite/gcc.dg/analyzer/data-model-9.c | 33 +
gcc/testsuite/gcc.dg/analyzer/data-model-path-1.c | 13 +
gcc/testsuite/gcc.dg/analyzer/disabling.c | 10 +
gcc/testsuite/gcc.dg/analyzer/dot-output.c | 33 +
.../gcc.dg/analyzer/double-free-lto-1-a.c | 16 +
.../gcc.dg/analyzer/double-free-lto-1-b.c | 8 +
gcc/testsuite/gcc.dg/analyzer/double-free-lto-1.h | 1 +
gcc/testsuite/gcc.dg/analyzer/equivalence.c | 31 +
gcc/testsuite/gcc.dg/analyzer/explode-1.c | 60 +
gcc/testsuite/gcc.dg/analyzer/explode-2.c | 50 +
gcc/testsuite/gcc.dg/analyzer/factorial.c | 7 +
gcc/testsuite/gcc.dg/analyzer/fibonacci.c | 9 +
gcc/testsuite/gcc.dg/analyzer/fields.c | 41 +
gcc/testsuite/gcc.dg/analyzer/file-1.c | 37 +
gcc/testsuite/gcc.dg/analyzer/file-2.c | 18 +
gcc/testsuite/gcc.dg/analyzer/function-ptr-1.c | 8 +
gcc/testsuite/gcc.dg/analyzer/function-ptr-2.c | 44 +
gcc/testsuite/gcc.dg/analyzer/function-ptr-3.c | 17 +
gcc/testsuite/gcc.dg/analyzer/gzio-2.c | 11 +
gcc/testsuite/gcc.dg/analyzer/gzio-3.c | 31 +
gcc/testsuite/gcc.dg/analyzer/gzio-3a.c | 27 +
gcc/testsuite/gcc.dg/analyzer/gzio.c | 17 +
gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c | 55 +
gcc/testsuite/gcc.dg/analyzer/loop-2.c | 37 +
gcc/testsuite/gcc.dg/analyzer/loop-2a.c | 40 +
gcc/testsuite/gcc.dg/analyzer/loop-3.c | 17 +
gcc/testsuite/gcc.dg/analyzer/loop-4.c | 43 +
gcc/testsuite/gcc.dg/analyzer/loop.c | 35 +
gcc/testsuite/gcc.dg/analyzer/malloc-1.c | 585 ++
gcc/testsuite/gcc.dg/analyzer/malloc-2.c | 23 +
gcc/testsuite/gcc.dg/analyzer/malloc-3.c | 8 +
gcc/testsuite/gcc.dg/analyzer/malloc-callbacks.c | 84 +
gcc/testsuite/gcc.dg/analyzer/malloc-dce.c | 12 +
gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c | 46 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-1.c | 24 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-10.c | 32 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-11.c | 96 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c | 7 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-13.c | 30 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c | 34 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-3.c | 23 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-4.c | 13 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-5.c | 13 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-6.c | 22 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-7.c | 29 +
.../gcc.dg/analyzer/malloc-ipa-8-double-free.c | 173 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-lto-a.c | 12 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-lto-b.c | 18 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-lto-c.c | 17 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8-lto.h | 12 +
.../gcc.dg/analyzer/malloc-ipa-8-unchecked.c | 67 +
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-9.c | 18 +
.../gcc.dg/analyzer/malloc-macro-inline-events.c | 46 +
.../gcc.dg/analyzer/malloc-macro-separate-events.c | 15 +
gcc/testsuite/gcc.dg/analyzer/malloc-macro.h | 2 +
.../gcc.dg/analyzer/malloc-many-paths-1.c | 14 +
.../gcc.dg/analyzer/malloc-many-paths-2.c | 30 +
.../gcc.dg/analyzer/malloc-many-paths-3.c | 36 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-1.c | 15 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-10.c | 20 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-2.c | 13 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-3.c | 14 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-4.c | 20 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-5.c | 43 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-6.c | 11 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-7.c | 21 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-8.c | 54 +
gcc/testsuite/gcc.dg/analyzer/malloc-paths-9.c | 299 +
gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1a.c | 181 +
gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-1b.c | 176 +
gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-2.c | 179 +
gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-3.c | 66 +
gcc/testsuite/gcc.dg/analyzer/malloc-vs-local-4.c | 40 +
gcc/testsuite/gcc.dg/analyzer/operations.c | 44 +
gcc/testsuite/gcc.dg/analyzer/params-2.c | 17 +
gcc/testsuite/gcc.dg/analyzer/params.c | 34 +
gcc/testsuite/gcc.dg/analyzer/paths-1.c | 18 +
gcc/testsuite/gcc.dg/analyzer/paths-1a.c | 18 +
gcc/testsuite/gcc.dg/analyzer/paths-2.c | 27 +
gcc/testsuite/gcc.dg/analyzer/paths-3.c | 49 +
gcc/testsuite/gcc.dg/analyzer/paths-4.c | 51 +
gcc/testsuite/gcc.dg/analyzer/paths-5.c | 12 +
gcc/testsuite/gcc.dg/analyzer/paths-6.c | 119 +
gcc/testsuite/gcc.dg/analyzer/paths-7.c | 59 +
gcc/testsuite/gcc.dg/analyzer/pattern-test-1.c | 28 +
gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c | 29 +
gcc/testsuite/gcc.dg/analyzer/pointer-merging.c | 16 +
gcc/testsuite/gcc.dg/analyzer/pr61861.c | 2 +
gcc/testsuite/gcc.dg/analyzer/pragma-1.c | 26 +
gcc/testsuite/gcc.dg/analyzer/scope-1.c | 23 +
gcc/testsuite/gcc.dg/analyzer/sensitive-1.c | 55 +
gcc/testsuite/gcc.dg/analyzer/setjmp-1.c | 1 +
gcc/testsuite/gcc.dg/analyzer/setjmp-2.c | 99 +
gcc/testsuite/gcc.dg/analyzer/setjmp-3.c | 108 +
gcc/testsuite/gcc.dg/analyzer/setjmp-4.c | 109 +
gcc/testsuite/gcc.dg/analyzer/setjmp-5.c | 67 +
gcc/testsuite/gcc.dg/analyzer/setjmp-6.c | 31 +
gcc/testsuite/gcc.dg/analyzer/setjmp-7.c | 36 +
gcc/testsuite/gcc.dg/analyzer/setjmp-7a.c | 111 +
gcc/testsuite/gcc.dg/analyzer/setjmp-8.c | 109 +
gcc/testsuite/gcc.dg/analyzer/setjmp-9.c | 111 +
gcc/testsuite/gcc.dg/analyzer/signal-1.c | 31 +
gcc/testsuite/gcc.dg/analyzer/signal-2.c | 34 +
gcc/testsuite/gcc.dg/analyzer/signal-3.c | 23 +
gcc/testsuite/gcc.dg/analyzer/signal-4a.c | 75 +
gcc/testsuite/gcc.dg/analyzer/signal-4b.c | 90 +
gcc/testsuite/gcc.dg/analyzer/strcmp-1.c | 35 +
gcc/testsuite/gcc.dg/analyzer/switch.c | 30 +
gcc/testsuite/gcc.dg/analyzer/taint-1.c | 128 +
gcc/testsuite/gcc.dg/analyzer/zlib-1.c | 69 +
gcc/testsuite/gcc.dg/analyzer/zlib-2.c | 51 +
gcc/testsuite/gcc.dg/analyzer/zlib-3.c | 214 +
gcc/testsuite/gcc.dg/analyzer/zlib-4.c | 20 +
gcc/testsuite/gcc.dg/analyzer/zlib-5.c | 51 +
gcc/testsuite/gcc.dg/analyzer/zlib-6.c | 47 +
gcc/testsuite/lib/gcc-defs.exp | 21 +
gcc/testsuite/lib/target-supports-dg.exp | 10 +
gcc/testsuite/lib/target-supports.exp | 21 +
gcc/timevar.def | 11 +
gcc/tree-pass.h | 1 +
gcc/tristate.cc | 221 +
gcc/tristate.h | 82 +
222 files changed, 40744 insertions(+), 8 deletions(-)
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index a5133c6d765..38165123654 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,81 @@
+2020-01-14 David Malcolm <dmalcolm@redhat.com>
+
+ * Makefile.in (lang_opt_files): Add analyzer.opt.
+ (ANALYZER_OBJS): New.
+ (OBJS): Add digraph.o, graphviz.o, ordered-hash-map-tests.o,
+ tristate.o and ANALYZER_OBJS.
+ (TEXI_GCCINT_FILES): Add analyzer.texi.
+ * common.opt (-fanalyzer): New driver option.
+ * config.in: Regenerate.
+ * configure: Regenerate.
+ * configure.ac (--disable-analyzer, ENABLE_ANALYZER): New option.
+ (gccdepdir): Also create depdir for "analyzer" subdir.
+ * digraph.cc: New file.
+ * digraph.h: New file.
+ * doc/analyzer.texi: New file.
+ * doc/gccint.texi ("Static Analyzer") New menu item.
+ (analyzer.texi): Include it.
+ * doc/invoke.texi ("Static Analyzer Options"): New list and new section.
+ ("Warning Options"): Add static analysis warnings to the list.
+ (-Wno-analyzer-double-fclose): New option.
+ (-Wno-analyzer-double-free): New option.
+ (-Wno-analyzer-exposure-through-output-file): New option.
+ (-Wno-analyzer-file-leak): New option.
+ (-Wno-analyzer-free-of-non-heap): New option.
+ (-Wno-analyzer-malloc-leak): New option.
+ (-Wno-analyzer-possible-null-argument): New option.
+ (-Wno-analyzer-possible-null-dereference): New option.
+ (-Wno-analyzer-null-argument): New option.
+ (-Wno-analyzer-null-dereference): New option.
+ (-Wno-analyzer-stale-setjmp-buffer): New option.
+ (-Wno-analyzer-tainted-array-index): New option.
+ (-Wno-analyzer-use-after-free): New option.
+ (-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
+ (-Wno-analyzer-use-of-uninitialized-value): New option.
+ (-Wanalyzer-too-complex): New option.
+ (-fanalyzer-call-summaries): New warning.
+ (-fanalyzer-checker=): New warning.
+ (-fanalyzer-fine-grained): New warning.
+ (-fno-analyzer-state-merge): New warning.
+ (-fno-analyzer-state-purge): New warning.
+ (-fanalyzer-transitivity): New warning.
+ (-fanalyzer-verbose-edges): New warning.
+ (-fanalyzer-verbose-state-changes): New warning.
+ (-fanalyzer-verbosity=): New warning.
+ (-fdump-analyzer): New warning.
+ (-fdump-analyzer-callgraph): New warning.
+ (-fdump-analyzer-exploded-graph): New warning.
+ (-fdump-analyzer-exploded-nodes): New warning.
+ (-fdump-analyzer-exploded-nodes-2): New warning.
+ (-fdump-analyzer-exploded-nodes-3): New warning.
+ (-fdump-analyzer-supergraph): New warning.
+ * doc/sourcebuild.texi (dg-require-dot): New.
+ (dg-check-dot): New.
+ * gdbinit.in (break-on-saved-diagnostic): New command.
+ * graphviz.cc: New file.
+ * graphviz.h: New file.
+ * ordered-hash-map-tests.cc: New file.
+ * ordered-hash-map.h: New file.
+ * passes.def (pass_analyzer): Add before
+ pass_ipa_whole_program_visibility.
+ * selftest-run-tests.c (selftest::run_tests): Call
+ selftest::ordered_hash_map_tests_cc_tests.
+ * selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
+ decl.
+ * shortest-paths.h: New file.
+ * timevar.def (TV_ANALYZER): New timevar.
+ (TV_ANALYZER_SUPERGRAPH): Likewise.
+ (TV_ANALYZER_STATE_PURGE): Likewise.
+ (TV_ANALYZER_PLAN): Likewise.
+ (TV_ANALYZER_SCC): Likewise.
+ (TV_ANALYZER_WORKLIST): Likewise.
+ (TV_ANALYZER_DUMP): Likewise.
+ (TV_ANALYZER_DIAGNOSTICS): Likewise.
+ (TV_ANALYZER_SHORTEST_PATHS): Likewise.
+ * tree-pass.h (make_pass_analyzer): New decl.
+ * tristate.cc: New file.
+ * tristate.h: New file.
+
2020-01-14 Uroš Bizjak <ubizjak@gmail.com>
PR target/93254
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 864f8e3ef2c..5c6d0a737e6 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -567,7 +567,7 @@ xm_include_list=@xm_include_list@
xm_defines=@xm_defines@
lang_checks=
lang_checks_parallelized=
-lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt
+lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
lang_specs_files=@lang_specs_files@
lang_tree_files=@lang_tree_files@
target_cpu_default=@target_cpu_default@
@@ -1214,6 +1214,32 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
c-family/c-ubsan.o c-family/known-headers.o \
c-family/c-attribs.o c-family/c-warn.o c-family/c-spellcheck.o
+# Analyzer object files
+ANALYZER_OBJS = \
+ analyzer/analysis-plan.o \
+ analyzer/analyzer.o \
+ analyzer/analyzer-logging.o \
+ analyzer/analyzer-pass.o \
+ analyzer/analyzer-selftests.o \
+ analyzer/call-string.o \
+ analyzer/checker-path.o \
+ analyzer/constraint-manager.o \
+ analyzer/diagnostic-manager.o \
+ analyzer/engine.o \
+ analyzer/pending-diagnostic.o \
+ analyzer/program-point.o \
+ analyzer/program-state.o \
+ analyzer/region-model.o \
+ analyzer/sm.o \
+ analyzer/sm-file.o \
+ analyzer/sm-malloc.o \
+ analyzer/sm-pattern-test.o \
+ analyzer/sm-sensitive.o \
+ analyzer/sm-signal.o \
+ analyzer/sm-taint.o \
+ analyzer/state-purge.o \
+ analyzer/supergraph.o
+
# Language-independent object files.
# We put the *-match.o and insn-*.o files first so that a parallel make
# will build them sooner, because they are large and otherwise tend to be
@@ -1283,6 +1309,7 @@ OBJS = \
df-problems.o \
df-scan.o \
dfp.o \
+ digraph.o \
dojump.o \
dominance.o \
domwalk.o \
@@ -1344,6 +1371,7 @@ OBJS = \
godump.o \
graph.o \
graphds.o \
+ graphviz.o \
graphite.o \
graphite-isl-ast-to-gimple.o \
graphite-dependences.o \
@@ -1443,6 +1471,7 @@ OBJS = \
optinfo-emit-json.o \
options-save.o \
opts-global.o \
+ ordered-hash-map-tests.o \
passes.o \
plugin.o \
postreload-gcse.o \
@@ -1600,6 +1629,7 @@ OBJS = \
tree-vector-builder.o \
tree-vrp.o \
tree.o \
+ tristate.o \
typed-splay-tree.o \
unique-ptr-tests.o \
valtrack.o \
@@ -1617,6 +1647,7 @@ OBJS = \
wide-int-print.o \
xcoffout.o \
$(out_object_file) \
+ $(ANALYZER_OBJS) \
$(EXTRA_OBJS) \
$(host_hook_obj)
@@ -3220,7 +3251,7 @@ TEXI_GCCINT_FILES = gccint.texi gcc-common.texi gcc-vers.texi \
gnu.texi gpl_v3.texi fdl.texi contrib.texi languages.texi \
sourcebuild.texi gty.texi libgcc.texi cfg.texi tree-ssa.texi \
loop.texi generic.texi gimple.texi plugins.texi optinfo.texi \
- match-and-simplify.texi ux.texi poly-int.texi
+ match-and-simplify.texi analyzer.texi ux.texi poly-int.texi
TEXI_GCCINSTALL_FILES = install.texi install-old.texi fdl.texi \
gcc-common.texi gcc-vers.texi
diff --git a/gcc/analyzer/ChangeLog b/gcc/analyzer/ChangeLog
new file mode 100644
index 00000000000..684124357b6
--- /dev/null
+++ b/gcc/analyzer/ChangeLog
@@ -0,0 +1,56 @@
+2020-01-14 David Malcolm <dmalcolm@redhat.com>
+
+ * ChangeLog: New file.
+ * analyzer-selftests.cc: New file.
+ * analyzer-selftests.h: New file.
+ * analyzer.opt: New file.
+ * analysis-plan.cc: New file.
+ * analysis-plan.h: New file.
+ * analyzer-logging.cc: New file.
+ * analyzer-logging.h: New file.
+ * analyzer-pass.cc: New file.
+ * analyzer.cc: New file.
+ * analyzer.h: New file.
+ * call-string.cc: New file.
+ * call-string.h: New file.
+ * checker-path.cc: New file.
+ * checker-path.h: New file.
+ * constraint-manager.cc: New file.
+ * constraint-manager.h: New file.
+ * diagnostic-manager.cc: New file.
+ * diagnostic-manager.h: New file.
+ * engine.cc: New file.
+ * engine.h: New file.
+ * exploded-graph.h: New file.
+ * pending-diagnostic.cc: New file.
+ * pending-diagnostic.h: New file.
+ * program-point.cc: New file.
+ * program-point.h: New file.
+ * program-state.cc: New file.
+ * program-state.h: New file.
+ * region-model.cc: New file.
+ * region-model.h: New file.
+ * sm-file.cc: New file.
+ * sm-malloc.cc: New file.
+ * sm-malloc.dot: New file.
+ * sm-pattern-test.cc: New file.
+ * sm-sensitive.cc: New file.
+ * sm-signal.cc: New file.
+ * sm-taint.cc: New file.
+ * sm.cc: New file.
+ * sm.h: New file.
+ * state-purge.cc: New file.
+ * state-purge.h: New file.
+ * supergraph.cc: New file.
+ * supergraph.h: New file.
+
+2019-12-13 David Malcolm <dmalcolm@redhat.com>
+
+ * Initial creation
+
+
+Copyright (C) 2019-2020 Free Software Foundation, Inc.
+
+Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.
diff --git a/gcc/analyzer/analysis-plan.cc b/gcc/analyzer/analysis-plan.cc
new file mode 100644
index 00000000000..8ad2fa2ebb4
--- /dev/null
+++ b/gcc/analyzer/analysis-plan.cc
@@ -0,0 +1,130 @@
+/* A class to encapsulate decisions about how the analysis should happen.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "options.h"
+#include "cgraph.h"
+#include "timevar.h"
+#include "ipa-utils.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "diagnostic-core.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/analysis-plan.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "function.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+
+#if ENABLE_ANALYZER
+
+/* class analysis_plan. */
+
+/* analysis_plan's ctor. */
+
+analysis_plan::analysis_plan (const supergraph &sg, logger *logger)
+: log_user (logger), m_sg (sg),
+ m_cgraph_node_postorder (XCNEWVEC (struct cgraph_node *,
+ symtab->cgraph_count)),
+ m_index_by_uid (symtab->cgraph_max_uid)
+{
+ LOG_SCOPE (logger);
+ auto_timevar time (TV_ANALYZER_PLAN);
+
+ m_num_cgraph_nodes = ipa_reverse_postorder (m_cgraph_node_postorder);
+ gcc_assert (m_num_cgraph_nodes == symtab->cgraph_count);
+ if (get_logger_file ())
+ ipa_print_order (get_logger_file (),
+ "analysis_plan", m_cgraph_node_postorder,
+ m_num_cgraph_nodes);
+
+ /* Populate m_index_by_uid. */
+ for (int i = 0; i < symtab->cgraph_max_uid; i++)
+ m_index_by_uid.quick_push (-1);
+ for (int i = 0; i < m_num_cgraph_nodes; i++)
+ {
+ gcc_assert (m_cgraph_node_postorder[i]->get_uid ()
+ < symtab->cgraph_max_uid);
+ m_index_by_uid[m_cgraph_node_postorder[i]->get_uid ()] = i;
+ }
+}
+
+/* analysis_plan's dtor. */
+
+analysis_plan::~analysis_plan ()
+{
+ free (m_cgraph_node_postorder);
+}
+
+/* Comparator for use by the exploded_graph's worklist, to order FUN_A
+ and FUN_B so that functions that are to be summarized are visited
+ before the summary is needed (based on a sort of the callgraph). */
+
+int
+analysis_plan::cmp_function (function *fun_a, function *fun_b) const
+{
+ cgraph_node *node_a = cgraph_node::get (fun_a->decl);
+ cgraph_node *node_b = cgraph_node::get (fun_b->decl);
+
+ int idx_a = m_index_by_uid[node_a->get_uid ()];
+ int idx_b = m_index_by_uid[node_b->get_uid ()];
+
+ return idx_b - idx_a;
+}
+
+/* Return true if the call EDGE should be analyzed using a call summary.
+ Return false if it should be analyzed using a full call and return. */
+
+bool
+analysis_plan::use_summary_p (const cgraph_edge *edge) const
+{
+ /* Don't use call summaries if -fno-analyzer-call-summaries. */
+ if (!flag_analyzer_call_summaries)
+ return false;
+
+ /* TODO: don't count callsites each time. */
+ int num_call_sites = 0;
+ const cgraph_node *callee = edge->callee;
+ for (cgraph_edge *edge = callee->callers; edge; edge = edge->next_caller)
+ ++num_call_sites;
+
+ /* Don't use a call summary if there's only one call site. */
+ if (num_call_sites <= 1)
+ return false;
+
+ /* Require the callee to be sufficiently complex to be worth
+ summarizing. */
+ if ((int)m_sg.get_num_snodes (callee->get_fun ())
+ < param_analyzer_min_snodes_for_call_summary)
+ return false;
+
+ return true;
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analysis-plan.h b/gcc/analyzer/analysis-plan.h
new file mode 100644
index 00000000000..a44c79561be
--- /dev/null
+++ b/gcc/analyzer/analysis-plan.h
@@ -0,0 +1,56 @@
+/* A class to encapsulate decisions about how the analysis should happen.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_ANALYSIS_PLAN_H
+#define GCC_ANALYZER_ANALYSIS_PLAN_H
+
+/* A class to encapsulate decisions about how the analysis should happen.
+ Examples:
+ - the order in which functions should be analyzed, so that function
+ summaries are created before analysis of call sites that might use
+ them
+ - which callgraph edges should use call summaries
+ TODO: the above is a work-in-progress. */
+
+class analysis_plan : public log_user
+{
+public:
+ analysis_plan (const supergraph &sg, logger *logger);
+ ~analysis_plan ();
+
+ int cmp_function (function *fun_a, function *fun_b) const;
+
+ bool use_summary_p (const cgraph_edge *edge) const;
+
+private:
+ DISABLE_COPY_AND_ASSIGN (analysis_plan);
+
+ const supergraph &m_sg;
+
+ /* Result of ipa_reverse_postorder. */
+ cgraph_node **m_cgraph_node_postorder;
+ int m_num_cgraph_nodes;
+
+ /* Index of each node within the postorder ordering,
+ accessed via the "m_uid" field. */
+ auto_vec<int> m_index_by_uid;
+};
+
+#endif /* GCC_ANALYZER_ANALYSIS_PLAN_H */
diff --git a/gcc/analyzer/analyzer-logging.cc b/gcc/analyzer/analyzer-logging.cc
new file mode 100644
index 00000000000..9a6b36eee3a
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.cc
@@ -0,0 +1,224 @@
+/* Hierarchical log messages for the analyzer.
+ Copyright (C) 2014-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "toplev.h" /* for print_version */
+#include "pretty-print.h" /* for print_version */
+#include "diagnostic.h"
+#include "tree-diagnostic.h"
+
+#include "analyzer/analyzer-logging.h"
+
+#if ENABLE_ANALYZER
+
+/* Implementation of class logger. */
+
+/* ctor for logger. */
+
+logger::logger (FILE *f_out,
+ int, /* flags */
+ int /* verbosity */,
+ const pretty_printer &reference_pp) :
+ m_refcount (0),
+ m_f_out (f_out),
+ m_indent_level (0),
+ m_log_refcount_changes (false),
+ m_pp (reference_pp.clone ())
+{
+ pp_show_color (m_pp) = 0;
+ pp_buffer (m_pp)->stream = f_out;
+
+ /* %qE in logs for SSA_NAMEs should show the ssa names, rather than
+ trying to prettify things by showing the underlying var. */
+ pp_format_decoder (m_pp) = default_tree_printer;
+
+ /* Begin the log by writing the GCC version. */
+ print_version (f_out, "", false);
+}
+
+/* The destructor for logger, invoked via
+ the decref method when the refcount hits zero.
+ Note that we do not close the underlying FILE * (m_f_out). */
+
+logger::~logger ()
+{
+ /* This should be the last message emitted. */
+ log ("%s", __PRETTY_FUNCTION__);
+ gcc_assert (m_refcount == 0);
+ delete m_pp;
+}
+
+/* Increment the reference count of the logger. */
+
+void
+logger::incref (const char *reason)
+{
+ m_refcount++;
+ if (m_log_refcount_changes)
+ log ("%s: reason: %s refcount now %i ",
+ __PRETTY_FUNCTION__, reason, m_refcount);
+}
+
+/* Decrement the reference count of the logger,
+ deleting it if nothing is referring to it. */
+
+void
+logger::decref (const char *reason)
+{
+ gcc_assert (m_refcount > 0);
+ --m_refcount;
+ if (m_log_refcount_changes)
+ log ("%s: reason: %s refcount now %i",
+ __PRETTY_FUNCTION__, reason, m_refcount);
+ if (m_refcount == 0)
+ delete this;
+}
+
+/* Write a formatted message to the log, by calling the log_va method. */
+
+void
+logger::log (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ log_va (fmt, &ap);
+ va_end (ap);
+}
+
+/* Write an indented line to the log file.
+
+ We explicitly flush after each line: if something crashes the process,
+ we want the logfile/stream to contain the most up-to-date hint about the
+ last thing that was happening, without it being hidden in an in-process
+ buffer. */
+
+void
+logger::log_va (const char *fmt, va_list *ap)
+{
+ start_log_line ();
+ log_va_partial (fmt, ap);
+ end_log_line ();
+}
+
+void
+logger::start_log_line ()
+{
+ for (int i = 0; i < m_indent_level; i++)
+ fputc (' ', m_f_out);
+}
+
+void
+logger::log_partial (const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ log_va_partial (fmt, &ap);
+ va_end (ap);
+}
+
+void
+logger::log_va_partial (const char *fmt, va_list *ap)
+{
+ text_info text;
+ text.format_spec = fmt;
+ text.args_ptr = ap;
+ text.err_no = 0;
+ pp_format (m_pp, &text);
+ pp_output_formatted_text (m_pp);
+}
+
+void
+logger::end_log_line ()
+{
+ pp_flush (m_pp);
+ pp_clear_output_area (m_pp);
+ fprintf (m_f_out, "\n");
+ fflush (m_f_out);
+}
+
+/* Record the entry within a particular scope, indenting subsequent
+ log lines accordingly. */
+
+void
+logger::enter_scope (const char *scope_name)
+{
+ log ("entering: %s", scope_name);
+ m_indent_level += 1;
+}
+
+void
+logger::enter_scope (const char *scope_name, const char *fmt, va_list *ap)
+{
+ start_log_line ();
+ log_partial ("entering: %s: ", scope_name);
+ log_va_partial (fmt, ap);
+ end_log_line ();
+
+ m_indent_level += 1;
+}
+
+
+/* Record the exit from a particular scope, restoring the indent level to
+ before the scope was entered. */
+
+void
+logger::exit_scope (const char *scope_name)
+{
+ if (m_indent_level)
+ m_indent_level -= 1;
+ else
+ log ("(mismatching indentation)");
+ log ("exiting: %s", scope_name);
+}
+
+/* Implementation of class log_user. */
+
+/* The constructor for log_user. */
+
+log_user::log_user (logger *logger) : m_logger (logger)
+{
+ if (m_logger)
+ m_logger->incref("log_user ctor");
+}
+
+/* The destructor for log_user. */
+
+log_user::~log_user ()
+{
+ if (m_logger)
+ m_logger->decref("log_user dtor");
+}
+
+/* Set the logger for a log_user, managing the reference counts
+ of the old and new logger (either of which might be NULL). */
+
+void
+log_user::set_logger (logger *logger)
+{
+ if (logger)
+ logger->incref ("log_user::set_logger");
+ if (m_logger)
+ m_logger->decref ("log_user::set_logger");
+ m_logger = logger;
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h
new file mode 100644
index 00000000000..9d5364bfa8f
--- /dev/null
+++ b/gcc/analyzer/analyzer-logging.h
@@ -0,0 +1,260 @@
+/* Hierarchical log messages for the analyzer.
+ Copyright (C) 2014-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+/* Adapted from jit-logging.h. */
+
+#ifndef ANALYZER_LOGGING_H
+#define ANALYZER_LOGGING_H
+
+/* A logger encapsulates a logging stream: a way to send
+ lines of pertinent information to a FILE *. */
+
+class logger
+{
+ public:
+ logger (FILE *f_out, int flags, int verbosity, const pretty_printer &reference_pp);
+ ~logger ();
+
+ void incref (const char *reason);
+ void decref (const char *reason);
+
+ void log (const char *fmt, ...)
+ ATTRIBUTE_GCC_DIAG(2, 3);
+ void log_va (const char *fmt, va_list *ap)
+ ATTRIBUTE_GCC_DIAG(2, 0);
+ void start_log_line ();
+ void log_partial (const char *fmt, ...)
+ ATTRIBUTE_GCC_DIAG(2, 3);
+ void log_va_partial (const char *fmt, va_list *ap)
+ ATTRIBUTE_GCC_DIAG(2, 0);
+ void end_log_line ();
+
+ void enter_scope (const char *scope_name);
+ void enter_scope (const char *scope_name, const char *fmt, va_list *ap)
+ ATTRIBUTE_GCC_DIAG(3, 0);
+ void exit_scope (const char *scope_name);
+
+ pretty_printer *get_printer () const { return m_pp; }
+ FILE *get_file () const { return m_f_out; }
+
+private:
+ DISABLE_COPY_AND_ASSIGN (logger);
+
+ int m_refcount;
+ FILE *m_f_out;
+ int m_indent_level;
+ bool m_log_refcount_changes;
+ pretty_printer *m_pp;
+};
+
+/* The class log_scope is an RAII-style class intended to make
+ it easy to notify a logger about entering and exiting the body of a
+ given function. */
+
+class log_scope
+{
+public:
+ log_scope (logger *logger, const char *name);
+ log_scope (logger *logger, const char *name, const char *fmt, ...)
+ ATTRIBUTE_GCC_DIAG(4, 5);
+ ~log_scope ();
+
+ private:
+ DISABLE_COPY_AND_ASSIGN (log_scope);
+
+ logger *m_logger;
+ const char *m_name;
+};
+
+/* The constructor for log_scope.
+
+ The normal case is that the logger is NULL, in which case this should
+ be largely a no-op.
+
+ If we do have a logger, notify it that we're entering the given scope.
+ We also need to hold a reference on it, to avoid a use-after-free
+ when logging the cleanup of the owner of the logger. */
+
+inline
+log_scope::log_scope (logger *logger, const char *name) :
+ m_logger (logger),
+ m_name (name)
+{
+ if (m_logger)
+ {
+ m_logger->incref ("log_scope ctor");
+ m_logger->enter_scope (m_name);
+ }
+}
+
+inline
+log_scope::log_scope (logger *logger, const char *name, const char *fmt, ...):
+ m_logger (logger),
+ m_name (name)
+{
+ if (m_logger)
+ {
+ m_logger->incref ("log_scope ctor");
+ va_list ap;
+ va_start (ap, fmt);
+ m_logger->enter_scope (m_name, fmt, &ap);
+ va_end (ap);
+ }
+}
+
+
+/* The destructor for log_scope; essentially the opposite of
+ the constructor. */
+
+inline
+log_scope::~log_scope ()
+{
+ if (m_logger)
+ {
+ m_logger->exit_scope (m_name);
+ m_logger->decref ("log_scope dtor");
+ }
+}
+
+/* A log_user is something that potentially uses a logger (which could be NULL).
+
+ The log_user class keeps the reference-count of a logger up-to-date. */
+
+class log_user
+{
+ public:
+ log_user (logger *logger);
+ ~log_user ();
+
+ logger * get_logger () const { return m_logger; }
+ void set_logger (logger * logger);
+
+ void log (const char *fmt, ...) const
+ ATTRIBUTE_GCC_DIAG(2, 3);
+
+ void start_log_line () const;
+ void end_log_line () const;
+
+ void enter_scope (const char *scope_name);
+ void exit_scope (const char *scope_name);
+
+ pretty_printer *get_logger_pp () const
+ {
+ gcc_assert (m_logger);
+ return m_logger->get_printer ();
+ }
+
+ FILE *get_logger_file () const
+ {
+ if (m_logger == NULL)
+ return NULL;
+ return m_logger->get_file ();
+ }
+
+ private:
+ DISABLE_COPY_AND_ASSIGN (log_user);
+
+ logger *m_logger;
+};
+
+/* A shortcut for calling log from a log_user, handling the common
+ case where the underlying logger is NULL via a no-op. */
+
+inline void
+log_user::log (const char *fmt, ...) const
+{
+ if (m_logger)
+ {
+ va_list ap;
+ va_start (ap, fmt);
+ m_logger->log_va (fmt, &ap);
+ va_end (ap);
+ }
+}
+
+/* A shortcut for starting a log line from a log_user,
+ handling the common case where the underlying logger is NULL via
+ a no-op. */
+
+inline void
+log_user::start_log_line () const
+{
+ if (m_logger)
+ m_logger->start_log_line ();
+}
+
+/* A shortcut for ending a log line from a log_user,
+ handling the common case where the underlying logger is NULL via
+ a no-op. */
+
+inline void
+log_user::end_log_line () const
+{
+ if (m_logger)
+ m_logger->end_log_line ();
+}
+
+/* A shortcut for recording entry into a scope from a log_user,
+ handling the common case where the underlying logger is NULL via
+ a no-op. */
+
+inline void
+log_user::enter_scope (const char *scope_name)
+{
+ if (m_logger)
+ m_logger->enter_scope (scope_name);
+}
+
+/* A shortcut for recording exit from a scope from a log_user,
+ handling the common case where the underlying logger is NULL via
+ a no-op. */
+
+inline void
+log_user::exit_scope (const char *scope_name)
+{
+ if (m_logger)
+ m_logger->exit_scope (scope_name);
+}
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+ it, identifying it using __PRETTY_FUNCTION__. */
+
+#define LOG_SCOPE(LOGGER) \
+ log_scope s (LOGGER, __PRETTY_FUNCTION__)
+
+/* If the given logger is non-NULL, log entry/exit of this scope to
+ it, identifying it using __func__. */
+
+#define LOG_FUNC(LOGGER) \
+ log_scope s (LOGGER, __func__)
+
+#define LOG_FUNC_1(LOGGER, FMT, A0) \
+ log_scope s (LOGGER, __func__, FMT, A0)
+
+#define LOG_FUNC_2(LOGGER, FMT, A0, A1) \
+ log_scope s (LOGGER, __func__, FMT, A0, A1)
+
+#define LOG_FUNC_3(LOGGER, FMT, A0, A1, A2) \
+ log_scope s (LOGGER, __func__, FMT, A0, A1, A2)
+
+#define LOG_FUNC_4(LOGGER, FMT, A0, A1, A2, A3) \
+ log_scope s (LOGGER, __func__, FMT, A0, A1, A2, A3)
+
+#endif /* ANALYZER_LOGGING_H */
diff --git a/gcc/analyzer/analyzer-pass.cc b/gcc/analyzer/analyzer-pass.cc
new file mode 100644
index 00000000000..4070e6d44b5
--- /dev/null
+++ b/gcc/analyzer/analyzer-pass.cc
@@ -0,0 +1,102 @@
+/* Integration of the analyzer with GCC's pass manager.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "context.h"
+#include "tree-pass.h"
+#include "diagnostic.h"
+#include "options.h"
+#include "analyzer/engine.h"
+
+namespace {
+
+/* Data for the analyzer pass. */
+
+const pass_data pass_data_analyzer =
+{
+ IPA_PASS, /* type */
+ "analyzer", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_ANALYZER, /* tv_id */
+ PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+/* The analyzer pass. */
+
+class pass_analyzer : public ipa_opt_pass_d
+{
+public:
+ pass_analyzer(gcc::context *ctxt)
+ : ipa_opt_pass_d (pass_data_analyzer, ctxt,
+ NULL, /* generate_summary */
+ NULL, /* write_summary */
+ NULL, /* read_summary */
+ NULL, /* write_optimization_summary */
+ NULL, /* read_optimization_summary */
+ NULL, /* stmt_fixup */
+ 0, /* function_transform_todo_flags_start */
+ NULL, /* function_transform */
+ NULL) /* variable_transform */
+ {}
+
+ /* opt_pass methods: */
+ bool gate (function *) FINAL OVERRIDE;
+ unsigned int execute (function *) FINAL OVERRIDE;
+}; // class pass_analyzer
+
+/* Only run the analyzer if -fanalyzer. */
+
+bool
+pass_analyzer::gate (function *)
+{
+ return flag_analyzer != 0;
+}
+
+/* Entrypoint for the analyzer pass. */
+
+unsigned int
+pass_analyzer::execute (function *)
+{
+#if ENABLE_ANALYZER
+ run_checkers ();
+#else
+ sorry ("%qs was not enabled in this build of GCC"
+ " (missing configure-time option %qs)",
+ "-fanalyzer", "--enable-analyzer");
+#endif
+
+ return 0;
+}
+
+} // anon namespace
+
+/* Make an instance of the analyzer pass. */
+
+ipa_opt_pass_d *
+make_pass_analyzer (gcc::context *ctxt)
+{
+ return new pass_analyzer (ctxt);
+}
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
new file mode 100644
index 00000000000..8f52ce2e45c
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -0,0 +1,61 @@
+/* Selftest support for the analyzer.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Build a VAR_DECL named NAME of type TYPE, simulating a file-level
+ static variable. */
+
+tree
+build_global_decl (const char *name, tree type)
+{
+ tree decl = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+ get_identifier (name), type);
+ TREE_STATIC (decl) = 1;
+ return decl;
+}
+
+/* Run all analyzer-specific selftests. */
+
+void
+run_analyzer_selftests ()
+{
+#if ENABLE_ANALYZER
+ analyzer_constraint_manager_cc_tests ();
+ analyzer_program_point_cc_tests ();
+ analyzer_program_state_cc_tests ();
+ analyzer_region_model_cc_tests ();
+#endif /* #if ENABLE_ANALYZER */
+}
+
+} /* end of namespace selftest. */
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
new file mode 100644
index 00000000000..6f08aa2b1bc
--- /dev/null
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -0,0 +1,44 @@
+/* Selftests for the analyzer.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_SELFTESTS_H
+#define GCC_ANALYZER_SELFTESTS_H
+
+#if CHECKING_P
+
+namespace selftest {
+
+extern tree build_global_decl (const char *name, tree type);
+
+extern void run_analyzer_selftests ();
+
+/* Declarations for specific families of tests (by source file), in
+ alphabetical order. */
+extern void analyzer_checker_script_cc_tests ();
+extern void analyzer_constraint_manager_cc_tests ();
+extern void analyzer_program_point_cc_tests ();
+extern void analyzer_program_state_cc_tests ();
+extern void analyzer_region_model_cc_tests ();
+
+} /* end of namespace selftest. */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_ANALYZER_SELFTESTS_H */
diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
new file mode 100644
index 00000000000..2a3ffaee852
--- /dev/null
+++ b/gcc/analyzer/analyzer.cc
@@ -0,0 +1,151 @@
+/* Utility functions for the analyzer.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+
+#if ENABLE_ANALYZER
+
+/* Helper function for checkers. Is the CALL to the given function name,
+ and with the given number of arguments?
+
+ This doesn't resolve function pointers via the region model;
+ is_named_call_p should be used instead, using a fndecl from
+ get_fndecl_for_call; this function should only be used for special cases
+ where it's not practical to get at the region model, or for special
+ analyzer functions such as __analyzer_dump. */
+
+bool
+is_special_named_call_p (const gcall *call, const char *funcname,
+ unsigned int num_args)
+{
+ gcc_assert (funcname);
+
+ tree fndecl = gimple_call_fndecl (call);
+ if (!fndecl)
+ return false;
+
+ return is_named_call_p (fndecl, funcname, call, num_args);
+}
+
+/* Helper function for checkers. Does FNDECL have the given FUNCNAME? */
+
+bool
+is_named_call_p (tree fndecl, const char *funcname)
+{
+ gcc_assert (fndecl);
+ gcc_assert (funcname);
+
+ return 0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname);
+}
+
+/* Helper function for checkers. Does FNDECL have the given FUNCNAME, and
+ does CALL have the given number of arguments? */
+
+bool
+is_named_call_p (tree fndecl, const char *funcname,
+ const gcall *call, unsigned int num_args)
+{
+ gcc_assert (fndecl);
+ gcc_assert (funcname);
+
+ if (!is_named_call_p (fndecl, funcname))
+ return false;
+
+ if (gimple_call_num_args (call) != num_args)
+ return false;
+
+ return true;
+}
+
+/* Return true if stmt is a setjmp call. */
+
+bool
+is_setjmp_call_p (const gimple *stmt)
+{
+ /* TODO: is there a less hacky way to check for "setjmp"? */
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ if (is_special_named_call_p (call, "_setjmp", 1))
+ return true;
+
+ return false;
+}
+
+/* Return true if stmt is a longjmp call. */
+
+bool
+is_longjmp_call_p (const gcall *call)
+{
+ /* TODO: is there a less hacky way to check for "longjmp"? */
+ if (is_special_named_call_p (call, "longjmp", 2))
+ return true;
+
+ return false;
+}
+
+/* Generate a label_text instance by formatting FMT, using a
+ temporary clone of the global_dc's printer (thus using its
+ formatting callbacks).
+
+ Colorize if the global_dc supports colorization and CAN_COLORIZE is
+ true. */
+
+label_text
+make_label_text (bool can_colorize, const char *fmt, ...)
+{
+ pretty_printer *pp = global_dc->printer->clone ();
+ pp_clear_output_area (pp);
+
+ if (!can_colorize)
+ pp_show_color (pp) = false;
+
+ text_info ti;
+ rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+ va_list ap;
+
+ va_start (ap, fmt);
+
+ ti.format_spec = _(fmt);
+ ti.args_ptr = ≈
+ ti.err_no = 0;
+ ti.x_data = NULL;
+ ti.m_richloc = &rich_loc;
+
+ pp_format (pp, &ti);
+ pp_output_formatted_text (pp);
+
+ va_end (ap);
+
+ label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+ delete pp;
+ return result;
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
new file mode 100644
index 00000000000..e207d7a9436
--- /dev/null
+++ b/gcc/analyzer/analyzer.h
@@ -0,0 +1,122 @@
+/* Utility functions for the analyzer.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_ANALYZER_H
+#define GCC_ANALYZER_ANALYZER_H
+
+/* Forward decls of common types, with indentation to show inheritance. */
+
+class graphviz_out;
+class supergraph;
+class supernode;
+class superedge;
+ class cfg_superedge;
+ class switch_cfg_superedge;
+ class callgraph_superedge;
+ class call_superedge;
+ class return_superedge;
+class svalue;
+ class region_svalue;
+ class constant_svalue;
+ class poisoned_svalue;
+ class unknown_svalue;
+ class setjmp_svalue;
+class region;
+ class map_region;
+ class symbolic_region;
+class region_model;
+class region_model_context;
+ class impl_region_model_context;
+class constraint_manager;
+class equiv_class;
+struct model_merger;
+struct svalue_id_merger_mapping;
+struct canonicalization;
+class pending_diagnostic;
+class state_change_event;
+class checker_path;
+class extrinsic_state;
+class sm_state_map;
+class stmt_finder;
+class program_point;
+class program_state;
+class exploded_graph;
+class exploded_node;
+class exploded_edge;
+class exploded_cluster;
+class exploded_path;
+class analysis_plan;
+class state_purge_map;
+class state_purge_per_ssa_name;
+class state_change;
+class rewind_info_t;
+
+extern bool is_special_named_call_p (const gcall *call, const char *funcname,
+ unsigned int num_args);
+extern bool is_named_call_p (tree fndecl, const char *funcname);
+extern bool is_named_call_p (tree fndecl, const char *funcname,
+ const gcall *call, unsigned int num_args);
+extern bool is_setjmp_call_p (const gimple *stmt);
+extern bool is_longjmp_call_p (const gcall *call);
+
+extern void register_analyzer_pass ();
+
+extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
+
+/* An RAII-style class for pushing/popping cfun within a scope.
+ Doing so ensures we get "In function " announcements
+ from the diagnostics subsystem. */
+
+class auto_cfun
+{
+public:
+ auto_cfun (function *fun) { push_cfun (fun); }
+ ~auto_cfun () { pop_cfun (); }
+};
+
+/* Begin suppressing -Wformat and -Wformat-extra-args. */
+
+#define PUSH_IGNORE_WFORMAT \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wformat\"") \
+ _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
+
+/* Finish suppressing -Wformat and -Wformat-extra-args. */
+
+#define POP_IGNORE_WFORMAT \
+ _Pragma("GCC diagnostic pop")
+
+/* A template for creating hash traits for a POD type. */
+
+template <typename Type>
+struct pod_hash_traits : typed_noop_remove<Type>
+{
+ typedef Type value_type;
+ typedef Type compare_type;
+ static inline hashval_t hash (value_type);
+ static inline bool equal (const value_type &existing,
+ const value_type &candidate);
+ static inline void mark_deleted (Type &);
+ static inline void mark_empty (Type &);
+ static inline bool is_deleted (Type);
+ static inline bool is_empty (Type);
+};
+
+#endif /* GCC_ANALYZER_ANALYZER_H */
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
new file mode 100644
index 00000000000..af8d81d697a
--- /dev/null
+++ b/gcc/analyzer/analyzer.opt
@@ -0,0 +1,181 @@
+; analyzer.opt -- Options for the analyzer.
+
+; Copyright (C) 2019-2020 Free Software Foundation, Inc.
+;
+; This file is part of GCC.
+;
+; GCC is free software; you can redistribute it and/or modify it under
+; the terms of the GNU General Public License as published by the Free
+; Software Foundation; either version 3, or (at your option) any later
+; version.
+;
+; GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+; WARRANTY; without even the implied warranty of MERCHANTABILITY or
+; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+; for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with GCC; see the file COPYING3. If not see
+; <http://www.gnu.org/licenses/>.
+
+; See the GCC internals manual for a description of this file's format.
+
+; Please try to keep this file in ASCII collating order.
+
+-param=analyzer-bb-explosion-factor=
+Common Joined UInteger Var(param_analyzer_bb_explosion_factor) Init(5) Param
+The maximum number of 'after supernode' exploded nodes within the analyzer per supernode, before terminating analysis.
+
+-param=analyzer-max-enodes-per-program-point=
+Common Joined UInteger Var(param_analyzer_max_enodes_per_program_point) Init(8) Param
+The maximum number of exploded nodes per program point within the analyzer, before terminating analysis of that point.
+
+-param=analyzer-max-recursion-depth=
+Common Joined UInteger Var(param_analyzer_max_recursion_depth) Init(2) Param
+The maximum number of times a callsite can appear in a call stack within the analyzer, before terminating analysis of a call tha would recurse deeper.
+
+-param=analyzer-min-snodes-for-call-summary=
+Common Joined UInteger Var(param_analyzer_min_snodes_for_call_summary) Init(10) Param
+The minimum number of supernodes within a function for the analyzer to consider summarizing its effects at call sites.
+
+Wanalyzer-double-fclose
+Common Var(warn_analyzer_double_fclose) Init(1) Warning
+Warn about code paths in which a stdio FILE can be closed more than once.
+
+Wanalyzer-double-free
+Common Var(warn_analyzer_double_free) Init(1) Warning
+Warn about code paths in which a pointer can be freed more than once.
+
+Wanalyzer-exposure-through-output-file
+Common Var(warn_analyzer_exposure_through_output_file) Init(1) Warning
+Warn about code paths in which sensitive data is written to a file.
+
+Wanalyzer-file-leak
+Common Var(warn_analyzer_file_leak) Init(1) Warning
+Warn about code paths in which a stdio FILE is not closed.
+
+Wanalyzer-free-of-non-heap
+Common Var(warn_analyzer_free_of_non_heap) Init(1) Warning
+Warn about code paths in which a non-heap pointer is freed.
+
+Wanalyzer-malloc-leak
+Common Var(warn_analyzer_malloc_leak) Init(1) Warning
+Warn about code paths in which a heap-allocated pointer leaks.
+
+Wanalyzer-possible-null-argument
+Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
+Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument.
+
+Wanalyzer-possible-null-dereference
+Common Var(warn_analyzer_possible_null_dereference) Init(1) Warning
+Warn about code paths in which a possibly-NULL pointer is dereferenced.
+
+Wanalyzer-unsafe-call-within-signal-handler
+Common Var(warn_analyzer_unsafe_call_within_signal_handler) Init(1) Warning
+Warn about code paths in which an async-signal-unsafe function is called from a signal handler.
+
+Wanalyzer-null-argument
+Common Var(warn_analyzer_null_argument) Init(1) Warning
+Warn about code paths in which NULL is passed to a must-not-be-NULL function argument.
+
+Wanalyzer-null-dereference
+Common Var(warn_analyzer_null_dereference) Init(1) Warning
+Warn about code paths in which a NULL pointer is dereferenced.
+
+Wanalyzer-stale-setjmp-buffer
+Common Var(warn_analyzer_stale_setjmp_buffer) Init(1) Warning
+Warn about code paths in which a longjmp rewinds to a jmp_buf saved in a stack frame that has returned.
+
+Wanalyzer-tainted-array-index
+Common Var(warn_analyzer_tainted_array_index) Init(1) Warning
+Warn about code paths in which an unsanitized value is used as an array index.
+
+Wanalyzer-use-after-free
+Common Var(warn_analyzer_use_after_free) Init(1) Warning
+Warn about code paths in which a freed value is used.
+
+Wanalyzer-use-of-pointer-in-stale-stack-frame
+Common Var(warn_analyzer_use_of_pointer_in_stale_stack_frame) Init(1) Warning
+Warn about code paths in which a pointer to a stale stack frame is used.
+
+Wanalyzer-use-of-uninitialized-value
+Common Var(warn_analyzer_use_of_uninitialized_value) Init(1) Warning
+Warn about code paths in which an initialized value is used.
+
+Wanalyzer-too-complex
+Common Var(warn_analyzer_too_complex) Init(0) Warning
+Warn if the code is too complicated for the analyzer to fully explore.
+
+fanalyzer-checker=
+Common Joined RejectNegative Var(flag_analyzer_checker)
+Restrict the analyzer to run just the named checker.
+
+fanalyzer-fine-grained
+Common Var(flag_analyzer_fine_grained) Init(0)
+Avoid combining multiple statements into one exploded edge.
+
+fanalyzer-state-purge
+Common Var(flag_analyzer_state_purge) Init(1)
+Purge unneeded state during analysis.
+
+fanalyzer-state-merge
+Common Var(flag_analyzer_state_merge) Init(1)
+Merge similar-enough states during analysis.
+
+fanalyzer-transitivity
+Common Var(flag_analyzer_transitivity) Init(0)
+Enable transitivity of constraints during analysis.
+
+fanalyzer-call-summaries
+Common Var(flag_analyzer_call_summaries) Init(0)
+Approximate the effect of function calls to simplify analysis.
+
+fanalyzer-verbose-edges
+Common Var(flag_analyzer_verbose_edges) Init(0)
+Emit more verbose descriptions of control flow in diagnostics.
+
+fanalyzer-verbose-state-changes
+Common Var(flag_analyzer_verbose_state_changes) Init(0)
+Emit more verbose descriptions of state changes in diagnostics.
+
+fanalyzer-verbosity=
+Common Joined UInteger Var(analyzer_verbosity) Init(2)
+Control which events are displayed in diagnostic paths.
+
+fdump-analyzer
+Common RejectNegative Var(flag_dump_analyzer)
+Dump internal details about what the analyzer is doing to SRCFILE.analyzer.txt.
+
+fdump-analyzer-stderr
+Common RejectNegative Var(flag_dump_analyzer_stderr)
+Dump internal details about what the analyzer is doing to stderr.
+
+fdump-analyzer-callgraph
+Common RejectNegative Var(flag_dump_analyzer_callgraph)
+Dump the analyzer supergraph to a SRCFILE.callgraph.dot file.
+
+fdump-analyzer-exploded-graph
+Common RejectNegative Var(flag_dump_analyzer_exploded_graph)
+Dump the analyzer exploded graph to a SRCFILE.eg.dot file.
+
+fdump-analyzer-exploded-nodes
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes)
+Emit diagnostics showing the location of nodes in the exploded graph.
+
+fdump-analyzer-exploded-nodes-2
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_2)
+Dump a textual representation of the exploded graph to SRCFILE.eg.txt.
+
+fdump-analyzer-exploded-nodes-3
+Common RejectNegative Var(flag_dump_analyzer_exploded_nodes_3)
+Dump a textual representation of the exploded graph to SRCFILE.eg-ID.txt.
+
+fdump-analyzer-state-purge
+Common RejectNegative Var(flag_dump_analyzer_state_purge)
+Dump state-purging information to a SRCFILE.state-purge.dot file.
+
+fdump-analyzer-supergraph
+Common RejectNegative Var(flag_dump_analyzer_supergraph)
+Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
+
+; This comment is to ensure we retain the blank line above.
diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc
new file mode 100644
index 00000000000..3d398c39a88
--- /dev/null
+++ b/gcc/analyzer/call-string.cc
@@ -0,0 +1,233 @@
+/* Call stacks at program points.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "tree.h"
+#include "options.h"
+#include "analyzer/call-string.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "function.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+
+#if ENABLE_ANALYZER
+
+/* class call_string. */
+
+/* call_string's copy ctor. */
+
+call_string::call_string (const call_string &other)
+: m_return_edges (other.m_return_edges.length ())
+{
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+ m_return_edges.quick_push (e);
+}
+
+/* call_string's assignment operator. */
+
+call_string&
+call_string::operator= (const call_string &other)
+{
+ // would be much simpler if we could rely on vec<> assignment op
+ m_return_edges.truncate (0);
+ m_return_edges.reserve (other.m_return_edges.length (), true);
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (other.m_return_edges, i, e)
+ m_return_edges.quick_push (e);
+ return *this;
+}
+
+/* call_string's equality operator. */
+
+bool
+call_string::operator== (const call_string &other) const
+{
+ if (m_return_edges.length () != other.m_return_edges.length ())
+ return false;
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_return_edges, i, e)
+ if (e != other.m_return_edges[i])
+ return false;
+ return true;
+}
+
+/* Print this to PP. */
+
+void
+call_string::print (pretty_printer *pp) const
+{
+ pp_string (pp, "[");
+
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_return_edges, i, e)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ pp_printf (pp, "(SN: %i -> SN: %i in %s)",
+ e->m_src->m_index, e->m_dest->m_index,
+ function_name (e->m_dest->m_fun));
+ }
+
+ pp_string (pp, "]");
+}
+
+/* Generate a hash value for this call_string. */
+
+hashval_t
+call_string::hash () const
+{
+ inchash::hash hstate;
+ int i;
+ const return_superedge *e;
+ FOR_EACH_VEC_ELT (m_return_edges, i, e)
+ hstate.add_ptr (e);
+ return hstate.end ();
+}
+
+/* Push the return superedge for CALL_SEDGE onto the end of this
+ call_string. */
+
+void
+call_string::push_call (const supergraph &sg,
+ const call_superedge *call_sedge)
+{
+ gcc_assert (call_sedge);
+ const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
+ gcc_assert (return_sedge);
+ m_return_edges.safe_push (return_sedge);
+}
+
+/* Count the number of times the top-most call site appears in the
+ stack. */
+
+int
+call_string::calc_recursion_depth () const
+{
+ if (m_return_edges.is_empty ())
+ return 0;
+ const return_superedge *top_return_sedge
+ = m_return_edges[m_return_edges.length () - 1];
+
+ int result = 0;
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_return_edges, i, e)
+ if (e == top_return_sedge)
+ ++result;
+ return result;
+}
+
+/* Comparator for call strings.
+ Return negative if A is before B.
+ Return positive if B is after A.
+ Return 0 if they are equal. */
+
+int
+call_string::cmp (const call_string &a,
+ const call_string &b)
+{
+ int result = cmp_1 (a, b);
+
+ /* Check that the ordering is symmetric */
+#if CHECKING_P
+ int reversed = cmp_1 (b, a);
+ gcc_assert (reversed == -result);
+#endif
+
+ /* We should only have 0 for equal pairs. */
+ gcc_assert (result != 0
+ || a == b);
+
+ return result;
+}
+
+/* Implementation of call_string::cmp.
+ This implements a version of lexicographical order. */
+
+int
+call_string::cmp_1 (const call_string &a,
+ const call_string &b)
+{
+ unsigned len_a = a.length ();
+ unsigned len_b = b.length ();
+
+ unsigned i = 0;
+ while (1)
+ {
+ /* Consider index i; the strings have been equal up to it. */
+
+ /* Have both strings run out? */
+ if (i >= len_a && i >= len_b)
+ return 0;
+
+ /* Otherwise, has just one of the strings run out? */
+ if (i >= len_a)
+ return 1;
+ if (i >= len_b)
+ return -1;
+
+ /* Otherwise, compare the edges. */
+ const return_superedge *edge_a = a[i];
+ const return_superedge *edge_b = b[i];
+ int src_cmp = edge_a->m_src->m_index - edge_b->m_src->m_index;
+ if (src_cmp)
+ return src_cmp;
+ int dest_cmp = edge_a->m_dest->m_index - edge_b->m_dest->m_index;
+ if (dest_cmp)
+ return dest_cmp;
+ i++;
+ // TODO: test coverage for this
+ }
+}
+
+/* Assert that this object is sane. */
+
+void
+call_string::validate () const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ /* Each entry's "caller" should be the "callee" of the previous entry. */
+ const return_superedge *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_return_edges, i, e)
+ if (i > 0)
+ gcc_assert (e->get_caller_function ()
+ == m_return_edges[i - 1]->get_callee_function ());
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h
new file mode 100644
index 00000000000..d0b25099336
--- /dev/null
+++ b/gcc/analyzer/call-string.h
@@ -0,0 +1,76 @@
+/* Call stacks at program points.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_CALL_STRING_H
+#define GCC_ANALYZER_CALL_STRING_H
+
+class supergraph;
+class call_superedge;
+class return_superedge;
+
+/* A string of return_superedge pointers, representing a call stack
+ at a program point.
+
+ This is used to ensure that we generate interprocedurally valid paths
+ i.e. that we return to the same callsite that called us.
+
+ The class actually stores the return edges, rather than the call edges,
+ since that's what we need to compare against. */
+
+class call_string
+{
+public:
+ call_string () : m_return_edges () {}
+ call_string (const call_string &other);
+ call_string& operator= (const call_string &other);
+
+ bool operator== (const call_string &other) const;
+
+ void print (pretty_printer *pp) const;
+
+ hashval_t hash () const;
+
+ bool empty_p () const { return m_return_edges.is_empty (); }
+
+ void push_call (const supergraph &sg,
+ const call_superedge *sedge);
+ const return_superedge *pop () { return m_return_edges.pop (); }
+
+ int calc_recursion_depth () const;
+
+ static int cmp (const call_string &a,
+ const call_string &b);
+
+ unsigned length () const { return m_return_edges.length (); }
+ const return_superedge *operator[] (unsigned idx) const
+ {
+ return m_return_edges[idx];
+ }
+
+ void validate () const;
+
+private:
+ static int cmp_1 (const call_string &a,
+ const call_string &b);
+
+ auto_vec<const return_superedge *> m_return_edges;
+};
+
+#endif /* GCC_ANALYZER_CALL_STRING_H */
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
new file mode 100644
index 00000000000..6c0f66cc275
--- /dev/null
+++ b/gcc/analyzer/checker-path.cc
@@ -0,0 +1,957 @@
+/* Subclasses of diagnostic_path and diagnostic_event for analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-pretty-print.h"
+#include "fold-const.h"
+#include "function.h"
+#include "diagnostic-path.h"
+#include "options.h"
+#include "cgraph.h"
+#include "function.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "diagnostic-event-id.h"
+#include "shortest-paths.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "analyzer/region-model.h"
+#include "analyzer/program-state.h"
+#include "analyzer/checker-path.h"
+#include "gimple-iterator.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/exploded-graph.h"
+
+#if ENABLE_ANALYZER
+
+/* Get a string for EK. */
+
+const char *
+event_kind_to_string (enum event_kind ek)
+{
+ switch (ek)
+ {
+ default:
+ gcc_unreachable ();
+ case EK_DEBUG:
+ return "EK_DEBUG";
+ case EK_CUSTOM:
+ return "EK_CUSTOM";
+ case EK_STMT:
+ return "EK_STMT";
+ case EK_FUNCTION_ENTRY:
+ return "EK_FUNCTION_ENTRY";
+ case EK_STATE_CHANGE:
+ return "EK_STATE_CHANGE";
+ case EK_START_CFG_EDGE:
+ return "EK_START_CFG_EDGE";
+ case EK_END_CFG_EDGE:
+ return "EK_END_CFG_EDGE";
+ case EK_CALL_EDGE:
+ return "EK_CALL_EDGE";
+ case EK_RETURN_EDGE:
+ return "EK_RETURN_EDGE";
+ case EK_SETJMP:
+ return "EK_SETJMP";
+ case EK_REWIND_FROM_LONGJMP:
+ return "EK_REWIND_FROM_LONGJMP";
+ case EK_REWIND_TO_SETJMP:
+ return "EK_REWIND_TO_SETJMP";
+ case EK_WARNING:
+ return "EK_WARNING";
+ }
+}
+
+/* class checker_event : public diagnostic_event. */
+
+/* Dump this event to PP (for debugging/logging purposes). */
+
+void
+checker_event::dump (pretty_printer *pp) const
+{
+ label_text event_desc (get_desc (false));
+ pp_printf (pp, "\"%s\" (depth %i, m_loc=%x)",
+ event_desc.m_buffer,
+ get_stack_depth (),
+ get_location ());
+ event_desc.maybe_free ();
+}
+
+/* Hook for being notified when this event has its final id EMISSION_ID
+ and is about to emitted for PD.
+
+ Base implementation of checker_event::prepare_for_emission vfunc;
+ subclasses that override this should chain up to it.
+
+ Record PD and EMISSION_ID, and call the get_desc vfunc, so that any
+ side-effects of the call to get_desc take place before
+ pending_diagnostic::emit is called.
+
+ For example, state_change_event::get_desc can call
+ pending_diagnostic::describe_state_change; free_of_non_heap can use this
+ to tweak the message (TODO: would be neater to simply capture the
+ pertinent data within the sm-state). */
+
+void
+checker_event::prepare_for_emission (checker_path *,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id)
+{
+ m_pending_diagnostic = pd;
+ m_emission_id = emission_id;
+
+ label_text desc = get_desc (false);
+ desc.maybe_free ();
+}
+
+/* class debug_event : public checker_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ debug_event.
+ Use the saved string as the event's description. */
+
+label_text
+debug_event::get_desc (bool) const
+{
+ return label_text::borrow (m_desc);
+}
+
+/* class custom_event : public checker_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ custom_event.
+ Use the saved string as the event's description. */
+
+label_text
+custom_event::get_desc (bool) const
+{
+ return label_text::borrow (m_desc);
+}
+
+/* class statement_event : public checker_event. */
+
+/* statement_event's ctor. */
+
+statement_event::statement_event (const gimple *stmt, tree fndecl, int depth,
+ const program_state &dst_state)
+: checker_event (EK_STMT, gimple_location (stmt), fndecl, depth),
+ m_stmt (stmt),
+ m_dst_state (dst_state)
+{
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ statement_event.
+ Use the statement's dump form as the event's description. */
+
+label_text
+statement_event::get_desc (bool) const
+{
+ pretty_printer pp;
+ pp_string (&pp, "stmt: ");
+ pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0);
+ return label_text::take (xstrdup (pp_formatted_text (&pp)));
+}
+
+/* class function_entry_event : public checker_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ function_entry_event.
+
+ Use a string such as "entry to 'foo'" as the event's description. */
+
+label_text
+function_entry_event::get_desc (bool can_colorize) const
+{
+ return make_label_text (can_colorize, "entry to %qE", m_fndecl);
+}
+
+/* class state_change_event : public checker_event. */
+
+/* state_change_event's ctor. */
+
+state_change_event::state_change_event (const supernode *node,
+ const gimple *stmt,
+ int stack_depth,
+ const state_machine &sm,
+ tree var,
+ state_machine::state_t from,
+ state_machine::state_t to,
+ tree origin,
+ const program_state &dst_state)
+: checker_event (EK_STATE_CHANGE,
+ stmt->location, node->m_fun->decl,
+ stack_depth),
+ m_node (node), m_stmt (stmt), m_sm (sm),
+ m_var (var), m_from (from), m_to (to),
+ m_origin (origin),
+ m_dst_state (dst_state)
+{
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ state_change_event.
+
+ Attempt to generate a nicer human-readable description.
+ For greatest precision-of-wording, give the pending diagnostic
+ a chance to describe this state change (in terms of the
+ diagnostic).
+ Note that we only have a pending_diagnostic set on the event once
+ the diagnostic is about to being emitted, so the description for
+ an event can change. */
+
+label_text
+state_change_event::get_desc (bool can_colorize) const
+{
+ if (m_pending_diagnostic)
+ {
+ label_text custom_desc
+ = m_pending_diagnostic->describe_state_change
+ (evdesc::state_change (can_colorize, m_var, m_origin,
+ m_from, m_to, m_emission_id, *this));
+ if (custom_desc.m_buffer)
+ {
+ if (flag_analyzer_verbose_state_changes)
+ {
+ /* Append debug version. */
+ label_text result;
+ if (m_origin)
+ result = make_label_text
+ (can_colorize,
+ "%s (state of %qE: %qs -> %qs, origin: %qE)",
+ custom_desc.m_buffer,
+ m_var,
+ m_sm.get_state_name (m_from),
+ m_sm.get_state_name (m_to),
+ m_origin);
+ else
+ result = make_label_text
+ (can_colorize,
+ "%s (state of %qE: %qs -> %qs, origin: NULL)",
+ custom_desc.m_buffer,
+ m_var,
+ m_sm.get_state_name (m_from),
+ m_sm.get_state_name (m_to));
+ custom_desc.maybe_free ();
+ return result;
+ }
+ else
+ return custom_desc;
+ }
+ }
+
+ /* Fallback description. */
+ if (m_var)
+ {
+ if (m_origin)
+ return make_label_text
+ (can_colorize,
+ "state of %qE: %qs -> %qs (origin: %qE)",
+ m_var,
+ m_sm.get_state_name (m_from),
+ m_sm.get_state_name (m_to),
+ m_origin);
+ else
+ return make_label_text
+ (can_colorize,
+ "state of %qE: %qs -> %qs (origin: NULL)",
+ m_var,
+ m_sm.get_state_name (m_from),
+ m_sm.get_state_name (m_to));
+ }
+ else
+ {
+ gcc_assert (m_origin == NULL_TREE);
+ return make_label_text
+ (can_colorize,
+ "global state: %qs -> %qs",
+ m_sm.get_state_name (m_from),
+ m_sm.get_state_name (m_to));
+ }
+}
+
+/* class superedge_event : public checker_event. */
+
+/* Get the callgraph_superedge for this superedge_event, which must be
+ for an interprocedural edge, rather than a CFG edge. */
+
+const callgraph_superedge&
+superedge_event::get_callgraph_superedge () const
+{
+ gcc_assert (m_sedge->m_kind != SUPEREDGE_CFG_EDGE);
+ return *m_sedge->dyn_cast_callgraph_superedge ();
+}
+
+/* Determine if this event should be filtered at the given verbosity
+ level. */
+
+bool
+superedge_event::should_filter_p (int verbosity) const
+{
+ switch (m_sedge->m_kind)
+ {
+ case SUPEREDGE_CFG_EDGE:
+ {
+ if (verbosity < 2)
+ return true;
+
+ if (verbosity == 2)
+ {
+ /* Filter events with empty descriptions. This ought to filter
+ FALLTHRU, but retain true/false/switch edges. */
+ label_text desc = get_desc (false);
+ gcc_assert (desc.m_buffer);
+ if (desc.m_buffer[0] == '\0')
+ return true;
+ desc.maybe_free ();
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+/* superedge_event's ctor. */
+
+superedge_event::superedge_event (enum event_kind kind,
+ const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+: checker_event (kind, loc, fndecl, depth),
+ m_eedge (eedge), m_sedge (eedge.m_sedge),
+ m_var (NULL_TREE), m_critical_state (0)
+{
+}
+
+/* class cfg_edge_event : public superedge_event. */
+
+/* Get the cfg_superedge for this cfg_edge_event. */
+
+const cfg_superedge &
+cfg_edge_event::get_cfg_superedge () const
+{
+ return *m_sedge->dyn_cast_cfg_superedge ();
+}
+
+/* cfg_edge_event's ctor. */
+
+cfg_edge_event::cfg_edge_event (enum event_kind kind,
+ const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+: superedge_event (kind, eedge, loc, fndecl, depth)
+{
+ gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE);
+}
+
+/* class start_cfg_edge_event : public cfg_edge_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ start_cfg_edge_event.
+
+ If -fanalyzer-verbose-edges, then generate low-level descriptions, such
+ as
+ "taking 'true' edge SN:7 -> SN:8".
+
+ Otherwise, generate strings using the label of the underlying CFG if
+ any, such as:
+ "following 'true' branch..." or
+ "following 'case 3' branch..."
+ "following 'default' branch..."
+
+ For conditionals, attempt to supply a description of the condition that
+ holds, such as:
+ "following 'false' branch (when 'ptr' is non-NULL)..."
+
+ Failing that, return an empty description (which will lead to this event
+ being filtered). */
+
+label_text
+start_cfg_edge_event::get_desc (bool can_colorize) const
+{
+ bool user_facing = !flag_analyzer_verbose_edges;
+ char *edge_desc = m_sedge->get_description (user_facing);
+ if (user_facing)
+ {
+ if (edge_desc && strlen (edge_desc) > 0)
+ {
+ label_text cond_desc = maybe_describe_condition (can_colorize);
+ label_text result;
+ if (cond_desc.m_buffer)
+ {
+ result = make_label_text (can_colorize,
+ "following %qs branch (%s)...",
+ edge_desc, cond_desc.m_buffer);
+ cond_desc.maybe_free ();
+ }
+ else
+ {
+ result = make_label_text (can_colorize,
+ "following %qs branch...",
+ edge_desc);
+ }
+ free (edge_desc);
+ return result;
+ }
+ else
+ {
+ free (edge_desc);
+ return label_text::borrow ("");
+ }
+ }
+ else
+ {
+ if (strlen (edge_desc) > 0)
+ {
+ label_text result
+ = make_label_text (can_colorize,
+ "taking %qs edge SN:%i -> SN:%i",
+ edge_desc,
+ m_sedge->m_src->m_index,
+ m_sedge->m_dest->m_index);
+ free (edge_desc);
+ return result;
+ }
+ else
+ {
+ free (edge_desc);
+ return make_label_text (can_colorize,
+ "taking edge SN:%i -> SN:%i",
+ m_sedge->m_src->m_index,
+ m_sedge->m_dest->m_index);
+ }
+ }
+}
+
+/* Attempt to generate a description of any condition that holds at this edge.
+
+ The intent is to make the user-facing messages more clear, especially for
+ cases where there's a single or double-negative, such as
+ when describing the false branch of an inverted condition.
+
+ For example, rather than printing just:
+
+ | if (!ptr)
+ | ~
+ | |
+ | (1) following 'false' branch...
+
+ it's clearer to spell out the condition that holds:
+
+ | if (!ptr)
+ | ~
+ | |
+ | (1) following 'false' branch (when 'ptr' is non-NULL)...
+ ^^^^^^^^^^^^^^^^^^^^^^
+
+ In the above example, this function would generate the highlighted
+ string: "when 'ptr' is non-NULL".
+
+ If the edge is not a condition, or it's not clear that a description of
+ the condition would be helpful to the user, return NULL. */
+
+label_text
+start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const
+{
+ const cfg_superedge& cfg_sedge = get_cfg_superedge ();
+
+ if (cfg_sedge.true_value_p () || cfg_sedge.false_value_p ())
+ {
+ const gimple *last_stmt = m_sedge->m_src->get_last_stmt ();
+ if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+ {
+ enum tree_code op = gimple_cond_code (cond_stmt);
+ tree lhs = gimple_cond_lhs (cond_stmt);
+ tree rhs = gimple_cond_rhs (cond_stmt);
+ if (cfg_sedge.false_value_p ())
+ op = invert_tree_comparison (op, false /* honor_nans */);
+ return maybe_describe_condition (can_colorize,
+ lhs, op, rhs);
+ }
+ }
+ return label_text::borrow (NULL);
+}
+
+/* Subroutine of maybe_describe_condition above.
+
+ Attempt to generate a user-facing description of the condition
+ LHS OP RHS, but only if it is likely to make it easier for the
+ user to understand a condition. */
+
+label_text
+start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
+ tree lhs,
+ enum tree_code op,
+ tree rhs)
+{
+ /* In theory we could just build a tree via
+ fold_build2 (op, boolean_type_node, lhs, rhs)
+ and print it with %qE on it, but this leads to warts such as
+ parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'. */
+
+ /* Special-case: describe testing the result of strcmp, as figuring
+ out what the "true" or "false" path is can be confusing to the user. */
+ if (TREE_CODE (lhs) == SSA_NAME
+ && zerop (rhs))
+ {
+ if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs)))
+ if (is_special_named_call_p (call, "strcmp", 2))
+ {
+ if (op == EQ_EXPR)
+ return label_text::borrow ("when the strings are equal");
+ if (op == NE_EXPR)
+ return label_text::borrow ("when the strings are non-equal");
+ }
+ }
+
+ /* Only attempt to generate text for sufficiently simple expressions. */
+ if (!should_print_expr_p (lhs))
+ return label_text::borrow (NULL);
+ if (!should_print_expr_p (rhs))
+ return label_text::borrow (NULL);
+
+ /* Special cases for pointer comparisons against NULL. */
+ if (POINTER_TYPE_P (TREE_TYPE (lhs))
+ && POINTER_TYPE_P (TREE_TYPE (rhs))
+ && zerop (rhs))
+ {
+ if (op == EQ_EXPR)
+ return make_label_text (can_colorize, "when %qE is NULL",
+ lhs);
+ if (op == NE_EXPR)
+ return make_label_text (can_colorize, "when %qE is non-NULL",
+ lhs);
+ }
+
+ return make_label_text (can_colorize, "when %<%E %s %E%>",
+ lhs, op_symbol_code (op), rhs);
+}
+
+/* Subroutine of maybe_describe_condition.
+
+ Return true if EXPR is we will get suitable user-facing output
+ from %E on it. */
+
+bool
+start_cfg_edge_event::should_print_expr_p (tree expr)
+{
+ if (TREE_CODE (expr) == SSA_NAME)
+ {
+ if (SSA_NAME_VAR (expr))
+ return should_print_expr_p (SSA_NAME_VAR (expr));
+ else
+ return false;
+ }
+
+ if (DECL_P (expr))
+ return true;
+
+ if (CONSTANT_CLASS_P (expr))
+ return true;
+
+ return false;
+}
+
+/* class call_event : public superedge_event. */
+
+/* call_event's ctor. */
+
+call_event::call_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+: superedge_event (EK_CALL_EDGE, eedge, loc, fndecl, depth)
+{
+ gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL);
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ call_event.
+
+ If this call event passes critical state for an sm-based warning,
+ allow the diagnostic to generate a precise description, such as:
+
+ "passing freed pointer 'ptr' in call to 'foo' from 'bar'"
+
+ Otherwise, generate a description of the form
+ "calling 'foo' from 'bar'". */
+
+label_text
+call_event::get_desc (bool can_colorize) const
+{
+ if (m_critical_state && m_pending_diagnostic)
+ {
+ gcc_assert (m_var);
+ label_text custom_desc
+ = m_pending_diagnostic->describe_call_with_state
+ (evdesc::call_with_state (can_colorize,
+ m_sedge->m_src->m_fun->decl,
+ m_sedge->m_dest->m_fun->decl,
+ m_var,
+ m_critical_state));
+ if (custom_desc.m_buffer)
+ return custom_desc;
+ }
+
+ return make_label_text (can_colorize,
+ "calling %qE from %qE",
+ m_sedge->m_dest->m_fun->decl,
+ m_sedge->m_src->m_fun->decl);
+}
+
+/* Override of checker_event::is_call_p for calls. */
+
+bool
+call_event::is_call_p () const
+{
+ return true;
+}
+
+/* class return_event : public superedge_event. */
+
+/* return_event's ctor. */
+
+return_event::return_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+: superedge_event (EK_RETURN_EDGE, eedge, loc, fndecl, depth)
+{
+ gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN);
+}
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ return_event.
+
+ If this return event returns critical state for an sm-based warning,
+ allow the diagnostic to generate a precise description, such as:
+
+ "possible of NULL to 'foo' from 'bar'"
+
+ Otherwise, generate a description of the form
+ "returning to 'foo' from 'bar'. */
+
+label_text
+return_event::get_desc (bool can_colorize) const
+{
+ /* For greatest precision-of-wording, if this is returning the
+ state involved in the pending diagnostic, give the pending
+ diagnostic a chance to describe this return (in terms of
+ itself). */
+ if (m_critical_state && m_pending_diagnostic)
+ {
+ label_text custom_desc
+ = m_pending_diagnostic->describe_return_of_state
+ (evdesc::return_of_state (can_colorize,
+ m_sedge->m_dest->m_fun->decl,
+ m_sedge->m_src->m_fun->decl,
+ m_critical_state));
+ if (custom_desc.m_buffer)
+ return custom_desc;
+ }
+ return make_label_text (can_colorize,
+ "returning to %qE from %qE",
+ m_sedge->m_dest->m_fun->decl,
+ m_sedge->m_src->m_fun->decl);
+}
+
+/* Override of checker_event::is_return_p for returns. */
+
+bool
+return_event::is_return_p () const
+{
+ return true;
+}
+
+/* class setjmp_event : public checker_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ setjmp_event. */
+
+label_text
+setjmp_event::get_desc (bool can_colorize) const
+{
+ return make_label_text (can_colorize,
+ "%qs called here",
+ "setjmp");
+}
+
+/* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event.
+
+ Record this setjmp's event ID into the path, so that rewind events can
+ use it. */
+
+void
+setjmp_event::prepare_for_emission (checker_path *path,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id)
+{
+ checker_event::prepare_for_emission (path, pd, emission_id);
+ path->record_setjmp_event (m_enode, emission_id);
+}
+
+/* class rewind_event : public checker_event. */
+
+/* Get the fndecl containing the site of the longjmp call. */
+
+tree
+rewind_event::get_longjmp_caller () const
+{
+ return m_eedge->m_src->get_function ()->decl;
+}
+
+/* Get the fndecl containing the site of the setjmp call. */
+
+tree
+rewind_event::get_setjmp_caller () const
+{
+ return m_eedge->m_dest->get_function ()->decl;
+}
+
+/* rewind_event's ctor. */
+
+rewind_event::rewind_event (const exploded_edge *eedge,
+ enum event_kind kind,
+ location_t loc, tree fndecl, int depth)
+: checker_event (kind, loc, fndecl, depth),
+ m_eedge (eedge)
+{
+ gcc_assert (m_eedge->m_custom_info); // a rewind_info_t
+}
+
+/* class rewind_from_longjmp_event : public rewind_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ rewind_from_longjmp_event. */
+
+label_text
+rewind_from_longjmp_event::get_desc (bool can_colorize) const
+{
+ const char *src_name = "longjmp";
+
+ if (get_longjmp_caller () == get_setjmp_caller ())
+ /* Special-case: purely intraprocedural rewind. */
+ return make_label_text (can_colorize,
+ "rewinding within %qE from %qs...",
+ get_longjmp_caller (),
+ src_name);
+ else
+ return make_label_text (can_colorize,
+ "rewinding from %qs in %qE...",
+ src_name,
+ get_longjmp_caller ());
+}
+
+/* class rewind_to_setjmp_event : public rewind_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ rewind_to_setjmp_event. */
+
+label_text
+rewind_to_setjmp_event::get_desc (bool can_colorize) const
+{
+ const char *dst_name = "setjmp";
+
+ /* If we can, identify the ID of the setjmp_event. */
+ if (m_original_setjmp_event_id.known_p ())
+ {
+ if (get_longjmp_caller () == get_setjmp_caller ())
+ /* Special-case: purely intraprocedural rewind. */
+ return make_label_text (can_colorize,
+ "...to %qs (saved at %@)",
+ dst_name,
+ &m_original_setjmp_event_id);
+ else
+ return make_label_text (can_colorize,
+ "...to %qs in %qE (saved at %@)",
+ dst_name,
+ get_setjmp_caller (),
+ &m_original_setjmp_event_id);
+ }
+ else
+ {
+ if (get_longjmp_caller () == get_setjmp_caller ())
+ /* Special-case: purely intraprocedural rewind. */
+ return make_label_text (can_colorize,
+ "...to %qs",
+ dst_name,
+ get_setjmp_caller ());
+ else
+ return make_label_text (can_colorize,
+ "...to %qs in %qE",
+ dst_name,
+ get_setjmp_caller ());
+ }
+}
+
+/* Implementation of checker_event::prepare_for_emission vfunc for
+ rewind_to_setjmp_event.
+
+ Attempt to look up the setjmp event ID that recorded the jmp_buf
+ for this rewind. */
+
+void
+rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id)
+{
+ checker_event::prepare_for_emission (path, pd, emission_id);
+ path->get_setjmp_event (m_rewind_info->get_enode_origin (),
+ &m_original_setjmp_event_id);
+}
+
+/* class warning_event : public checker_event. */
+
+/* Implementation of diagnostic_event::get_desc vfunc for
+ warning_event.
+
+ If the pending diagnostic implements describe_final_event, use it,
+ generating a precise description e.g.
+ "second 'free' here; first 'free' was at (7)"
+
+ Otherwise generate a generic description. */
+
+label_text
+warning_event::get_desc (bool can_colorize) const
+{
+ if (m_pending_diagnostic)
+ {
+ label_text ev_desc
+ = m_pending_diagnostic->describe_final_event
+ (evdesc::final_event (can_colorize, m_var, m_state));
+ if (ev_desc.m_buffer)
+ {
+ if (m_sm && flag_analyzer_verbose_state_changes)
+ {
+ label_text result
+ = make_label_text (can_colorize,
+ "%s (%qE is in state %qs)",
+ ev_desc.m_buffer,
+ m_var,m_sm->get_state_name (m_state));
+ ev_desc.maybe_free ();
+ return result;
+ }
+ else
+ return ev_desc;
+ }
+ }
+
+ if (m_sm)
+ return make_label_text (can_colorize,
+ "here (%qE is in state %qs)",
+ m_var,
+ m_sm->get_state_name (m_state));
+ else
+ return label_text::borrow ("here");
+}
+
+/* Print a single-line representation of this path to PP. */
+
+void
+checker_path::dump (pretty_printer *pp) const
+{
+ pp_character (pp, '[');
+
+ checker_event *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_events, i, e)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ label_text event_desc (e->get_desc (false));
+ pp_printf (pp, "\"%s\"", event_desc.m_buffer);
+ event_desc.maybe_free ();
+ }
+ pp_character (pp, ']');
+}
+
+/* Print a multiline form of this path to LOGGER, prefixing it with DESC. */
+
+void
+checker_path::maybe_log (logger *logger, const char *desc) const
+{
+ if (!logger)
+ return;
+ logger->start_log_line ();
+ logger->log_partial ("%s: ", desc);
+ dump (logger->get_printer ());
+ logger->end_log_line ();
+ for (unsigned i = 0; i < m_events.length (); i++)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("%s[%i]: %s ", desc, i,
+ event_kind_to_string (m_events[i]->m_kind));
+ m_events[i]->dump (logger->get_printer ());
+ logger->end_log_line ();
+ }
+}
+
+/* Print a multiline form of this path to STDERR. */
+
+DEBUG_FUNCTION void
+checker_path::debug () const
+{
+ checker_event *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_events, i, e)
+ {
+ label_text event_desc (e->get_desc (false));
+ fprintf (stderr,
+ "[%i]: %s \"%s\"\n",
+ i,
+ event_kind_to_string (m_events[i]->m_kind),
+ event_desc.m_buffer);
+ event_desc.maybe_free ();
+ }
+}
+
+/* Add a warning_event to the end of this path. */
+
+void
+checker_path::add_final_event (const state_machine *sm,
+ const exploded_node *enode, const gimple *stmt,
+ tree var, state_machine::state_t state)
+{
+ checker_event *end_of_path
+ = new warning_event (stmt->location,
+ enode->get_function ()->decl,
+ enode->get_stack_depth (),
+ sm, var, state);
+ add_event (end_of_path);
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
new file mode 100644
index 00000000000..cabbeb4fd4f
--- /dev/null
+++ b/gcc/analyzer/checker-path.h
@@ -0,0 +1,586 @@
+/* Subclasses of diagnostic_path and diagnostic_event for analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_CHECKER_PATH_H
+#define GCC_ANALYZER_CHECKER_PATH_H
+
+/* An enum for discriminating between the concrete subclasses of
+ checker_event. */
+
+enum event_kind
+{
+ EK_DEBUG,
+ EK_CUSTOM,
+ EK_STMT,
+ EK_FUNCTION_ENTRY,
+ EK_STATE_CHANGE,
+ EK_START_CFG_EDGE,
+ EK_END_CFG_EDGE,
+ EK_CALL_EDGE,
+ EK_RETURN_EDGE,
+ EK_SETJMP,
+ EK_REWIND_FROM_LONGJMP,
+ EK_REWIND_TO_SETJMP,
+ EK_WARNING
+};
+
+extern const char *event_kind_to_string (enum event_kind ek);
+
+/* Event subclasses.
+
+ The class hierarchy looks like this (using indentation to show
+ inheritance, and with event_kinds shown for the concrete subclasses):
+
+ diagnostic_event
+ checker_event
+ debug_event (EK_DEBUG)
+ custom_event (EK_CUSTOM)
+ statement_event (EK_STMT)
+ function_entry_event (EK_FUNCTION_ENTRY)
+ state_change_event (EK_STATE_CHANGE)
+ superedge_event
+ cfg_edge_event
+ start_cfg_edge_event (EK_START_CFG_EDGE)
+ end_cfg_edge_event (EK_END_CFG_EDGE)
+ call_event (EK_CALL_EDGE)
+ return_edge (EK_RETURN_EDGE)
+ setjmp_event (EK_SETJMP)
+ rewind_event
+ rewind_from_longjmp_event (EK_REWIND_FROM_LONGJMP)
+ rewind_to_setjmp_event (EK_REWIND_TO_SETJMP)
+ warning_event (EK_WARNING). */
+
+/* Abstract subclass of diagnostic_event; the base class for use in
+ checker_path (the analyzer's diagnostic_path subclass). */
+
+class checker_event : public diagnostic_event
+{
+public:
+ checker_event (enum event_kind kind,
+ location_t loc, tree fndecl, int depth)
+ : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
+ m_pending_diagnostic (NULL), m_emission_id ()
+ {
+ }
+
+ /* Implementation of diagnostic_event. */
+
+ location_t get_location () const FINAL OVERRIDE { return m_loc; }
+ tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+ int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+
+ /* Additional functionality. */
+
+ virtual checker_event *clone () const = 0;
+
+ virtual void prepare_for_emission (checker_path *,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id);
+ virtual bool is_call_p () const { return false; }
+ virtual bool is_function_entry_p () const { return false; }
+ virtual bool is_return_p () const { return false; }
+
+ void dump (pretty_printer *pp) const;
+
+ public:
+ const enum event_kind m_kind;
+ protected:
+ location_t m_loc;
+ tree m_fndecl;
+ int m_depth;
+ pending_diagnostic *m_pending_diagnostic;
+ diagnostic_event_id_t m_emission_id; // only set once all pruning has occurred
+};
+
+/* A concrete event subclass for a purely textual event, for use in
+ debugging path creation and filtering. */
+
+class debug_event : public checker_event
+{
+public:
+ debug_event (location_t loc, tree fndecl, int depth,
+ const char *desc)
+ : checker_event (EK_DEBUG, loc, fndecl, depth),
+ m_desc (xstrdup (desc))
+ {
+ }
+ ~debug_event ()
+ {
+ free (m_desc);
+ }
+
+ label_text get_desc (bool) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new debug_event (m_loc, m_fndecl, m_depth, m_desc);
+ }
+
+private:
+ char *m_desc;
+};
+
+/* A concrete event subclass for custom events. These are not filtered,
+ as they are likely to be pertinent to the diagnostic. */
+
+class custom_event : public checker_event
+{
+public:
+ custom_event (location_t loc, tree fndecl, int depth,
+ const char *desc)
+ : checker_event (EK_CUSTOM, loc, fndecl, depth),
+ m_desc (xstrdup (desc))
+ {
+ }
+ ~custom_event ()
+ {
+ free (m_desc);
+ }
+
+ label_text get_desc (bool) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new custom_event (m_loc, m_fndecl, m_depth, m_desc);
+ }
+
+private:
+ char *m_desc;
+};
+
+/* A concrete event subclass describing the execution of a gimple statement,
+ for use at high verbosity levels when debugging paths. */
+
+class statement_event : public checker_event
+{
+public:
+ statement_event (const gimple *stmt, tree fndecl, int depth,
+ const program_state &dst_state);
+
+ label_text get_desc (bool) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new statement_event (m_stmt, m_fndecl, m_depth, m_dst_state);
+ }
+
+ const gimple * const m_stmt;
+ const program_state m_dst_state;
+};
+
+/* An event subclass describing the entry to a function. */
+
+class function_entry_event : public checker_event
+{
+public:
+ function_entry_event (location_t loc, tree fndecl, int depth)
+ : checker_event (EK_FUNCTION_ENTRY, loc, fndecl, depth)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new function_entry_event (m_loc, m_fndecl, m_depth);
+ }
+
+ bool is_function_entry_p () const FINAL OVERRIDE { return true; }
+};
+
+/* Subclass of checker_event describing a state change. */
+
+class state_change_event : public checker_event
+{
+public:
+ state_change_event (const supernode *node, const gimple *stmt,
+ int stack_depth,
+ const state_machine &sm,
+ tree var,
+ state_machine::state_t from,
+ state_machine::state_t to,
+ tree origin,
+ const program_state &dst_state);
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new state_change_event (m_node, m_stmt, m_depth,
+ m_sm, m_var, m_from, m_to, m_origin,
+ m_dst_state);
+ }
+
+ region_id get_lvalue (tree expr) const
+ {
+ return m_dst_state.m_region_model->get_lvalue (expr, NULL);
+ }
+
+ const supernode *m_node;
+ const gimple *m_stmt;
+ const state_machine &m_sm;
+ tree m_var;
+ state_machine::state_t m_from;
+ state_machine::state_t m_to;
+ tree m_origin;
+ program_state m_dst_state;
+};
+
+/* Subclass of checker_event; parent class for subclasses that relate to
+ a superedge. */
+
+class superedge_event : public checker_event
+{
+public:
+ /* Mark this edge event as being either an interprocedural call or
+ return in which VAR is in STATE, and that this is critical to the
+ diagnostic (so that get_desc can attempt to get a better description
+ from any pending_diagnostic). */
+ void record_critical_state (tree var, state_machine::state_t state)
+ {
+ m_var = var;
+ m_critical_state = state;
+ }
+
+ const callgraph_superedge& get_callgraph_superedge () const;
+
+ bool should_filter_p (int verbosity) const;
+
+ protected:
+ superedge_event (enum event_kind kind, const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth);
+
+ public:
+ const exploded_edge &m_eedge;
+ const superedge *m_sedge;
+ tree m_var;
+ state_machine::state_t m_critical_state;
+};
+
+/* An abstract event subclass for when a CFG edge is followed; it has two
+ subclasses, representing the start of the edge and the end of the
+ edge, which come in pairs. */
+
+class cfg_edge_event : public superedge_event
+{
+public:
+ const cfg_superedge& get_cfg_superedge () const;
+
+ protected:
+ cfg_edge_event (enum event_kind kind, const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth);
+};
+
+/* A concrete event subclass for the start of a CFG edge
+ e.g. "following 'false' branch...'. */
+
+class start_cfg_edge_event : public cfg_edge_event
+{
+public:
+ start_cfg_edge_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+ : cfg_edge_event (EK_START_CFG_EDGE, eedge, loc, fndecl, depth)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new start_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+ }
+
+ private:
+ label_text maybe_describe_condition (bool can_colorize) const;
+
+ static label_text maybe_describe_condition (bool can_colorize,
+ tree lhs,
+ enum tree_code op,
+ tree rhs);
+ static bool should_print_expr_p (tree);
+};
+
+/* A concrete event subclass for the end of a CFG edge
+ e.g. "...to here'. */
+
+class end_cfg_edge_event : public cfg_edge_event
+{
+public:
+ end_cfg_edge_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth)
+ : cfg_edge_event (EK_END_CFG_EDGE, eedge, loc, fndecl, depth)
+ {
+ }
+
+ label_text get_desc (bool /*can_colorize*/) const FINAL OVERRIDE
+ {
+ return label_text::borrow ("...to here");
+ }
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new end_cfg_edge_event (m_eedge, m_loc, m_fndecl, m_depth);
+ }
+};
+
+/* A concrete event subclass for an interprocedural call. */
+
+class call_event : public superedge_event
+{
+public:
+ call_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth);
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new call_event (m_eedge, m_loc, m_fndecl, m_depth);
+ }
+
+ bool is_call_p () const FINAL OVERRIDE;
+};
+
+/* A concrete event subclass for an interprocedural return. */
+
+class return_event : public superedge_event
+{
+public:
+ return_event (const exploded_edge &eedge,
+ location_t loc, tree fndecl, int depth);
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ checker_event *clone () const FINAL OVERRIDE
+ {
+ return new return_event (m_eedge, m_loc, m_fndecl, m_depth);
+ }
+
+ bool is_return_p () const FINAL OVERRIDE;
+};
+
+/* A concrete event subclass for a setjmp call. */
+
+class setjmp_event : public checker_event
+{
+public:
+ setjmp_event (location_t loc, const exploded_node *enode,
+ tree fndecl, int depth)
+ : checker_event (EK_SETJMP, loc, fndecl, depth),
+ m_enode (enode)
+ {
+ }
+
+ setjmp_event *clone () const FINAL OVERRIDE
+ {
+ return new setjmp_event (m_loc, m_enode, m_fndecl, m_depth);
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ void prepare_for_emission (checker_path *path,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id) FINAL OVERRIDE;
+
+private:
+ const exploded_node *m_enode;
+};
+
+/* An abstract event subclass for rewinding from a longjmp to a setjmp.
+ Base class for two from/to subclasses, showing the two halves of the
+ rewind. */
+
+class rewind_event : public checker_event
+{
+public:
+ tree get_longjmp_caller () const;
+ tree get_setjmp_caller () const;
+ const exploded_edge *get_eedge () const { return m_eedge; }
+
+ protected:
+ rewind_event (const exploded_edge *eedge,
+ enum event_kind kind,
+ location_t loc, tree fndecl, int depth);
+
+ private:
+ const exploded_edge *m_eedge;
+};
+
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+ showing the longjmp. */
+
+class rewind_from_longjmp_event : public rewind_event
+{
+public:
+ rewind_from_longjmp_event (const exploded_edge *eedge,
+ location_t loc, tree fndecl, int depth)
+ : rewind_event (eedge, EK_REWIND_FROM_LONGJMP, loc, fndecl, depth)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ rewind_from_longjmp_event *clone () const FINAL OVERRIDE
+ {
+ return new rewind_from_longjmp_event (get_eedge (),
+ m_loc, m_fndecl, m_depth);
+ }
+};
+
+/* A concrete event subclass for rewinding from a longjmp to a setjmp,
+ showing the setjmp. */
+
+class rewind_to_setjmp_event : public rewind_event
+{
+public:
+ rewind_to_setjmp_event (const exploded_edge *eedge,
+ location_t loc, tree fndecl, int depth,
+ const rewind_info_t *rewind_info)
+ : rewind_event (eedge, EK_REWIND_TO_SETJMP, loc, fndecl, depth),
+ m_rewind_info (rewind_info)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ rewind_to_setjmp_event *clone () const FINAL OVERRIDE
+ {
+ return new rewind_to_setjmp_event (get_eedge (),
+ m_loc, m_fndecl, m_depth,
+ m_rewind_info);
+ }
+
+ void prepare_for_emission (checker_path *path,
+ pending_diagnostic *pd,
+ diagnostic_event_id_t emission_id) FINAL OVERRIDE;
+
+private:
+ diagnostic_event_id_t m_original_setjmp_event_id;
+ const rewind_info_t *m_rewind_info;
+};
+
+/* Concrete subclass of checker_event for use at the end of a path:
+ a repeat of the warning message at the end of the path (perhaps with
+ references to pertinent events that occurred on the way), at the point
+ where the problem occurs. */
+
+class warning_event : public checker_event
+{
+public:
+ warning_event (location_t loc, tree fndecl, int depth,
+ const state_machine *sm,
+ tree var, state_machine::state_t state)
+ : checker_event (EK_WARNING, loc, fndecl, depth),
+ m_sm (sm), m_var (var), m_state (state)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+
+ warning_event *clone () const FINAL OVERRIDE
+ {
+ return new warning_event (m_loc, m_fndecl, m_depth, m_sm, m_var, m_state);
+ }
+
+private:
+ const state_machine *m_sm;
+ tree m_var;
+ state_machine::state_t m_state;
+};
+
+/* Subclass of diagnostic_path for analyzer diagnostics. */
+
+class checker_path : public diagnostic_path
+{
+public:
+ checker_path () : diagnostic_path () {}
+
+ /* Implementation of diagnostic_path vfuncs. */
+
+ unsigned num_events () const FINAL OVERRIDE
+ {
+ return m_events.length ();
+ }
+
+ const diagnostic_event & get_event (int idx) const FINAL OVERRIDE
+ {
+ return *m_events[idx];
+ }
+
+ void dump (pretty_printer *pp) const;
+ void debug () const;
+
+ void maybe_log (logger *logger, const char *desc) const;
+
+ void add_event (checker_event *event)
+ {
+ m_events.safe_push (event);
+ }
+
+ void delete_event (int idx)
+ {
+ checker_event *event = m_events[idx];
+ m_events.ordered_remove (idx);
+ delete event;
+ }
+
+ void add_final_event (const state_machine *sm,
+ const exploded_node *enode, const gimple *stmt,
+ tree var, state_machine::state_t state);
+
+ /* After all event-pruning, a hook for notifying each event what
+ its ID will be. The events are notified in order, allowing
+ for later events to refer to the IDs of earlier events in
+ their descriptions. */
+ void prepare_for_emission (pending_diagnostic *pd)
+ {
+ checker_event *e;
+ int i;
+ FOR_EACH_VEC_ELT (m_events, i, e)
+ e->prepare_for_emission (this, pd, diagnostic_event_id_t (i));
+ }
+
+ void record_setjmp_event (const exploded_node *enode,
+ diagnostic_event_id_t setjmp_emission_id)
+ {
+ m_setjmp_event_ids.put (enode, setjmp_emission_id);
+ }
+
+ bool get_setjmp_event (const exploded_node *enode,
+ diagnostic_event_id_t *out_emission_id)
+ {
+ if (diagnostic_event_id_t *emission_id = m_setjmp_event_ids.get (enode))
+ {
+ *out_emission_id = *emission_id;
+ return true;
+ }
+ return false;
+ }
+
+ /* The events that have occurred along this path. */
+ auto_delete_vec<checker_event> m_events;
+
+ /* During prepare_for_emission (and after), the setjmp_event for each
+ exploded_node *, so that rewind events can refer to them in their
+ descriptions. */
+ hash_map <const exploded_node *, diagnostic_event_id_t> m_setjmp_event_ids;
+};
+
+#endif /* GCC_ANALYZER_CHECKER_PATH_H */
diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
new file mode 100644
index 00000000000..669d604e1b7
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.cc
@@ -0,0 +1,2260 @@
+/* Tracking equivalence classes and constraints at a point on an execution path.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "fold-const.h"
+#include "selftest.h"
+#include "graphviz.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if ENABLE_ANALYZER
+
+/* One of the end-points of a range. */
+
+struct bound
+{
+ bound () : m_constant (NULL_TREE), m_closed (false) {}
+ bound (tree constant, bool closed)
+ : m_constant (constant), m_closed (closed) {}
+
+ void ensure_closed (bool is_upper);
+
+ const char * get_relation_as_str () const;
+
+ tree m_constant;
+ bool m_closed;
+};
+
+/* A range of values, used for determining if a value has been
+ constrained to just one possible constant value. */
+
+struct range
+{
+ range () : m_lower_bound (), m_upper_bound () {}
+ range (const bound &lower, const bound &upper)
+ : m_lower_bound (lower), m_upper_bound (upper) {}
+
+ void dump (pretty_printer *pp) const;
+
+ bool constrained_to_single_element (tree *out);
+
+ bound m_lower_bound;
+ bound m_upper_bound;
+};
+
+/* struct bound. */
+
+/* Ensure that this bound is closed by converting an open bound to a
+ closed one. */
+
+void
+bound::ensure_closed (bool is_upper)
+{
+ if (!m_closed)
+ {
+ /* Offset by 1 in the appropriate direction.
+ For example, convert 3 < x into 4 <= x,
+ and convert x < 5 into x <= 4. */
+ gcc_assert (CONSTANT_CLASS_P (m_constant));
+ m_constant = fold_build2 (is_upper ? MINUS_EXPR : PLUS_EXPR,
+ TREE_TYPE (m_constant),
+ m_constant, integer_one_node);
+ gcc_assert (CONSTANT_CLASS_P (m_constant));
+ m_closed = true;
+ }
+}
+
+/* Get "<=" vs "<" for this bound. */
+
+const char *
+bound::get_relation_as_str () const
+{
+ if (m_closed)
+ return "<=";
+ else
+ return "<";
+}
+
+/* struct range. */
+
+/* Dump this range to PP, which must support %E for tree. */
+
+void
+range::dump (pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qE %s x %s %qE",
+ m_lower_bound.m_constant,
+ m_lower_bound.get_relation_as_str (),
+ m_upper_bound.get_relation_as_str (),
+ m_upper_bound.m_constant);
+POP_IGNORE_WFORMAT
+}
+
+/* Determine if there is only one possible value for this range.
+ If so, return true and write the constant to *OUT.
+ Otherwise, return false. */
+
+bool
+range::constrained_to_single_element (tree *out)
+{
+ if (!INTEGRAL_TYPE_P (TREE_TYPE (m_lower_bound.m_constant)))
+ return false;
+ if (!INTEGRAL_TYPE_P (TREE_TYPE (m_upper_bound.m_constant)))
+ return false;
+
+ /* Convert any open bounds to closed bounds. */
+ m_lower_bound.ensure_closed (false);
+ m_upper_bound.ensure_closed (true);
+
+ // Are they equal?
+ tree comparison
+ = fold_build2 (EQ_EXPR, boolean_type_node,
+ m_lower_bound.m_constant,
+ m_upper_bound.m_constant);
+ if (comparison == boolean_true_node)
+ {
+ *out = m_lower_bound.m_constant;
+ return true;
+ }
+ else
+ return false;
+}
+
+/* class equiv_class. */
+
+/* equiv_class's default ctor. */
+
+equiv_class::equiv_class ()
+: m_constant (NULL_TREE), m_cst_sid (svalue_id::null ()),
+ m_vars ()
+{
+}
+
+/* equiv_class's copy ctor. */
+
+equiv_class::equiv_class (const equiv_class &other)
+: m_constant (other.m_constant), m_cst_sid (other.m_cst_sid),
+ m_vars (other.m_vars.length ())
+{
+ int i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (other.m_vars, i, sid)
+ m_vars.quick_push (*sid);
+}
+
+/* Print an all-on-one-line representation of this equiv_class to PP,
+ which must support %E for trees. */
+
+void
+equiv_class::print (pretty_printer *pp) const
+{
+ pp_character (pp, '{');
+ int i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (m_vars, i, sid)
+ {
+ if (i > 0)
+ pp_string (pp, " == ");
+ sid->print (pp);
+ }
+ if (m_constant)
+ {
+ if (i > 0)
+ pp_string (pp, " == ");
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qE", m_constant);
+POP_IGNORE_WFORMAT
+ }
+ pp_character (pp, '}');
+}
+
+/* Generate a hash value for this equiv_class. */
+
+hashval_t
+equiv_class::hash () const
+{
+ inchash::hash hstate;
+ int i;
+ svalue_id *sid;
+
+ inchash::add_expr (m_constant, hstate);
+ FOR_EACH_VEC_ELT (m_vars, i, sid)
+ inchash::add (*sid, hstate);
+ return hstate.end ();
+}
+
+/* Equality operator for equiv_class. */
+
+bool
+equiv_class::operator== (const equiv_class &other)
+{
+ if (m_constant != other.m_constant)
+ return false; // TODO: use tree equality here?
+
+ /* FIXME: should we compare m_cst_sid? */
+
+ if (m_vars.length () != other.m_vars.length ())
+ return false;
+
+ int i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (m_vars, i, sid)
+ if (! (*sid == other.m_vars[i]))
+ return false;
+
+ return true;
+}
+
+/* Add SID to this equiv_class, using CM to check if it's a constant. */
+
+void
+equiv_class::add (svalue_id sid, const constraint_manager &cm)
+{
+ gcc_assert (!sid.null_p ());
+ if (tree cst = cm.maybe_get_constant (sid))
+ {
+ gcc_assert (CONSTANT_CLASS_P (cst));
+ /* FIXME: should we canonicalize which svalue is the constant
+ when there are multiple equal constants? */
+ m_constant = cst;
+ m_cst_sid = sid;
+ }
+ m_vars.safe_push (sid);
+}
+
+/* Remove SID from this equivalence class.
+ Return true if SID was the last var in the equivalence class (suggesting
+ a possible leak). */
+
+bool
+equiv_class::del (svalue_id sid)
+{
+ gcc_assert (!sid.null_p ());
+ gcc_assert (sid != m_cst_sid);
+
+ int i;
+ svalue_id *iv;
+ FOR_EACH_VEC_ELT (m_vars, i, iv)
+ {
+ if (*iv == sid)
+ {
+ m_vars[i] = m_vars[m_vars.length () - 1];
+ m_vars.pop ();
+ return m_vars.length () == 0;
+ }
+ }
+
+ /* SID must be in the class. */
+ gcc_unreachable ();
+ return false;
+}
+
+/* Get a representative member of this class, for handling cases
+ where the IDs can change mid-traversal. */
+
+svalue_id
+equiv_class::get_representative () const
+{
+ if (!m_cst_sid.null_p ())
+ return m_cst_sid;
+ else
+ {
+ gcc_assert (m_vars.length () > 0);
+ return m_vars[0];
+ }
+}
+
+/* Remap all svalue_ids within this equiv_class using MAP. */
+
+void
+equiv_class::remap_svalue_ids (const svalue_id_map &map)
+{
+ int i;
+ svalue_id *iv;
+ FOR_EACH_VEC_ELT (m_vars, i, iv)
+ map.update (iv);
+ map.update (&m_cst_sid);
+}
+
+/* Comparator for use by equiv_class::canonicalize. */
+
+static int
+svalue_id_cmp_by_id (const void *p1, const void *p2)
+{
+ const svalue_id *sid1 = (const svalue_id *)p1;
+ const svalue_id *sid2 = (const svalue_id *)p2;
+ return sid1->as_int () - sid2->as_int ();
+}
+
+/* Sort the svalues_ids within this equiv_class. */
+
+void
+equiv_class::canonicalize ()
+{
+ m_vars.qsort (svalue_id_cmp_by_id);
+}
+
+/* Get a debug string for C_OP. */
+
+const char *
+constraint_op_code (enum constraint_op c_op)
+{
+ switch (c_op)
+ {
+ default:
+ gcc_unreachable ();
+ case CONSTRAINT_NE: return "!=";
+ case CONSTRAINT_LT: return "<";
+ case CONSTRAINT_LE: return "<=";
+ }
+}
+
+/* Convert C_OP to an enum tree_code. */
+
+enum tree_code
+constraint_tree_code (enum constraint_op c_op)
+{
+ switch (c_op)
+ {
+ default:
+ gcc_unreachable ();
+ case CONSTRAINT_NE: return NE_EXPR;
+ case CONSTRAINT_LT: return LT_EXPR;
+ case CONSTRAINT_LE: return LE_EXPR;
+ }
+}
+
+/* Given "lhs C_OP rhs", determine "lhs T_OP rhs".
+
+ For example, given "x < y", then "x > y" is false. */
+
+static tristate
+eval_constraint_op_for_op (enum constraint_op c_op, enum tree_code t_op)
+{
+ switch (c_op)
+ {
+ default:
+ gcc_unreachable ();
+ case CONSTRAINT_NE:
+ if (t_op == EQ_EXPR)
+ return tristate (tristate::TS_FALSE);
+ if (t_op == NE_EXPR)
+ return tristate (tristate::TS_TRUE);
+ break;
+ case CONSTRAINT_LT:
+ if (t_op == LT_EXPR || t_op == LE_EXPR || t_op == NE_EXPR)
+ return tristate (tristate::TS_TRUE);
+ if (t_op == EQ_EXPR || t_op == GT_EXPR || t_op == GE_EXPR)
+ return tristate (tristate::TS_FALSE);
+ break;
+ case CONSTRAINT_LE:
+ if (t_op == LE_EXPR)
+ return tristate (tristate::TS_TRUE);
+ if (t_op == GT_EXPR)
+ return tristate (tristate::TS_FALSE);
+ break;
+ }
+ return tristate (tristate::TS_UNKNOWN);
+}
+
+/* class constraint. */
+
+/* Print this constraint to PP (which must support %E for trees),
+ using CM to look up equiv_class instances from ids. */
+
+void
+constraint::print (pretty_printer *pp, const constraint_manager &cm) const
+{
+ m_lhs.print (pp);
+ pp_string (pp, ": ");
+ m_lhs.get_obj (cm).print (pp);
+ pp_string (pp, " ");
+ pp_string (pp, constraint_op_code (m_op));
+ pp_string (pp, " ");
+ m_rhs.print (pp);
+ pp_string (pp, ": ");
+ m_rhs.get_obj (cm).print (pp);
+}
+
+/* Generate a hash value for this constraint. */
+
+hashval_t
+constraint::hash () const
+{
+ inchash::hash hstate;
+ hstate.add_int (m_lhs.m_idx);
+ hstate.add_int (m_op);
+ hstate.add_int (m_rhs.m_idx);
+ return hstate.end ();
+}
+
+/* Equality operator for constraints. */
+
+bool
+constraint::operator== (const constraint &other) const
+{
+ if (m_lhs != other.m_lhs)
+ return false;
+ if (m_op != other.m_op)
+ return false;
+ if (m_rhs != other.m_rhs)
+ return false;
+ return true;
+}
+
+/* class equiv_class_id. */
+
+/* Get the underlying equiv_class for this ID from CM. */
+
+const equiv_class &
+equiv_class_id::get_obj (const constraint_manager &cm) const
+{
+ return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* Access the underlying equiv_class for this ID from CM. */
+
+equiv_class &
+equiv_class_id::get_obj (constraint_manager &cm) const
+{
+ return cm.get_equiv_class_by_index (m_idx);
+}
+
+/* Print this equiv_class_id to PP. */
+
+void
+equiv_class_id::print (pretty_printer *pp) const
+{
+ if (null_p ())
+ pp_printf (pp, "null");
+ else
+ pp_printf (pp, "ec%i", m_idx);
+}
+
+/* class constraint_manager. */
+
+/* constraint_manager's copy ctor. */
+
+constraint_manager::constraint_manager (const constraint_manager &other)
+: m_equiv_classes (other.m_equiv_classes.length ()),
+ m_constraints (other.m_constraints.length ())
+{
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+ m_equiv_classes.quick_push (new equiv_class (*ec));
+ constraint *c;
+ FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+ m_constraints.quick_push (*c);
+}
+
+/* constraint_manager's assignment operator. */
+
+constraint_manager&
+constraint_manager::operator= (const constraint_manager &other)
+{
+ gcc_assert (m_equiv_classes.length () == 0);
+ gcc_assert (m_constraints.length () == 0);
+
+ int i;
+ equiv_class *ec;
+ m_equiv_classes.reserve (other.m_equiv_classes.length ());
+ FOR_EACH_VEC_ELT (other.m_equiv_classes, i, ec)
+ m_equiv_classes.quick_push (new equiv_class (*ec));
+ constraint *c;
+ m_constraints.reserve (other.m_constraints.length ());
+ FOR_EACH_VEC_ELT (other.m_constraints, i, c)
+ m_constraints.quick_push (*c);
+
+ return *this;
+}
+
+/* Generate a hash value for this constraint_manager. */
+
+hashval_t
+constraint_manager::hash () const
+{
+ inchash::hash hstate;
+ int i;
+ equiv_class *ec;
+ constraint *c;
+
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ hstate.merge_hash (ec->hash ());
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ hstate.merge_hash (c->hash ());
+ return hstate.end ();
+}
+
+/* Equality operator for constraint_manager. */
+
+bool
+constraint_manager::operator== (const constraint_manager &other) const
+{
+ if (m_equiv_classes.length () != other.m_equiv_classes.length ())
+ return false;
+ if (m_constraints.length () != other.m_constraints.length ())
+ return false;
+
+ int i;
+ equiv_class *ec;
+
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ if (!(*ec == *other.m_equiv_classes[i]))
+ return false;
+
+ constraint *c;
+
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ if (!(*c == other.m_constraints[i]))
+ return false;
+
+ return true;
+}
+
+/* Print this constraint_manager to PP (which must support %E for trees). */
+
+void
+constraint_manager::print (pretty_printer *pp) const
+{
+ pp_string (pp, "{");
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ equiv_class_id (i).print (pp);
+ pp_string (pp, ": ");
+ ec->print (pp);
+ }
+ pp_string (pp, " | ");
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ if (i > 0)
+ pp_string (pp, " && ");
+ c->print (pp, *this);
+ }
+ pp_printf (pp, "}");
+}
+
+/* Dump a multiline representation of this constraint_manager to PP
+ (which must support %E for trees). */
+
+void
+constraint_manager::dump_to_pp (pretty_printer *pp) const
+{
+ // TODO
+ pp_string (pp, " equiv classes:");
+ pp_newline (pp);
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ pp_string (pp, " ");
+ equiv_class_id (i).print (pp);
+ pp_string (pp, ": ");
+ ec->print (pp);
+ pp_newline (pp);
+ }
+ pp_string (pp, " constraints:");
+ pp_newline (pp);
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ pp_printf (pp, " %i: ", i);
+ c->print (pp, *this);
+ pp_newline (pp);
+ }
+}
+
+/* Dump a multiline representation of this constraint_manager to FP. */
+
+void
+constraint_manager::dump (FILE *fp) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = fp;
+ dump_to_pp (&pp);
+ pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this constraint_manager to stderr. */
+
+DEBUG_FUNCTION void
+constraint_manager::dump () const
+{
+ dump (stderr);
+}
+
+/* Dump a multiline representation of CM to stderr. */
+
+DEBUG_FUNCTION void
+debug (const constraint_manager &cm)
+{
+ cm.dump ();
+}
+
+/* Attempt to add the constraint LHS OP RHS to this constraint_manager.
+ Return true if the constraint could be added (or is already true).
+ Return false if the constraint contradicts existing knowledge. */
+
+bool
+constraint_manager::add_constraint (svalue_id lhs,
+ enum tree_code op,
+ svalue_id rhs)
+{
+ equiv_class_id lhs_ec_id = get_or_add_equiv_class (lhs);
+ equiv_class_id rhs_ec_id = get_or_add_equiv_class (rhs);
+ return add_constraint (lhs_ec_id, op,rhs_ec_id);
+}
+
+/* Attempt to add the constraint LHS_EC_ID OP RHS_EC_ID to this
+ constraint_manager.
+ Return true if the constraint could be added (or is already true).
+ Return false if the constraint contradicts existing knowledge. */
+
+bool
+constraint_manager::add_constraint (equiv_class_id lhs_ec_id,
+ enum tree_code op,
+ equiv_class_id rhs_ec_id)
+{
+ tristate t = eval_condition (lhs_ec_id, op, rhs_ec_id);
+
+ /* Discard constraints that are already known. */
+ if (t.is_true ())
+ return true;
+
+ /* Reject unsatisfiable constraints. */
+ if (t.is_false ())
+ return false;
+
+ gcc_assert (lhs_ec_id != rhs_ec_id);
+
+ /* For now, simply accumulate constraints, without attempting any further
+ optimization. */
+ switch (op)
+ {
+ case EQ_EXPR:
+ {
+ /* Merge rhs_ec into lhs_ec. */
+ equiv_class &lhs_ec_obj = lhs_ec_id.get_obj (*this);
+ const equiv_class &rhs_ec_obj = rhs_ec_id.get_obj (*this);
+
+ int i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (rhs_ec_obj.m_vars, i, sid)
+ lhs_ec_obj.add (*sid, *this);
+
+ if (rhs_ec_obj.m_constant)
+ {
+ //gcc_assert (lhs_ec_obj.m_constant == NULL);
+ lhs_ec_obj.m_constant = rhs_ec_obj.m_constant;
+ }
+
+ /* Drop rhs equivalence class, overwriting it with the
+ final ec (which might be the same one). */
+ equiv_class_id final_ec_id = m_equiv_classes.length () - 1;
+ equiv_class *old_ec = m_equiv_classes[rhs_ec_id.m_idx];
+ equiv_class *final_ec = m_equiv_classes.pop ();
+ if (final_ec != old_ec)
+ m_equiv_classes[rhs_ec_id.m_idx] = final_ec;
+ delete old_ec;
+
+ /* Update the constraints. */
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ /* Update references to the rhs_ec so that
+ they refer to the lhs_ec. */
+ if (c->m_lhs == rhs_ec_id)
+ c->m_lhs = lhs_ec_id;
+ if (c->m_rhs == rhs_ec_id)
+ c->m_rhs = lhs_ec_id;
+
+ /* Renumber all constraints that refer to the final rhs_ec
+ to the old rhs_ec, where the old final_ec now lives. */
+ if (c->m_lhs == final_ec_id)
+ c->m_lhs = rhs_ec_id;
+ if (c->m_rhs == final_ec_id)
+ c->m_rhs = rhs_ec_id;
+ }
+ }
+ break;
+ case GE_EXPR:
+ add_constraint_internal (rhs_ec_id, CONSTRAINT_LE, lhs_ec_id);
+ break;
+ case LE_EXPR:
+ add_constraint_internal (lhs_ec_id, CONSTRAINT_LE, rhs_ec_id);
+ break;
+ case NE_EXPR:
+ add_constraint_internal (lhs_ec_id, CONSTRAINT_NE, rhs_ec_id);
+ break;
+ case GT_EXPR:
+ add_constraint_internal (rhs_ec_id, CONSTRAINT_LT, lhs_ec_id);
+ break;
+ case LT_EXPR:
+ add_constraint_internal (lhs_ec_id, CONSTRAINT_LT, rhs_ec_id);
+ break;
+ default:
+ /* do nothing. */
+ break;
+ }
+ validate ();
+ return true;
+}
+
+/* Subroutine of constraint_manager::add_constraint, for handling all
+ operations other than equality (for which equiv classes are merged). */
+
+void
+constraint_manager::add_constraint_internal (equiv_class_id lhs_id,
+ enum constraint_op c_op,
+ equiv_class_id rhs_id)
+{
+ /* Add the constraint. */
+ m_constraints.safe_push (constraint (lhs_id, c_op, rhs_id));
+
+ if (!flag_analyzer_transitivity)
+ return;
+
+ if (c_op != CONSTRAINT_NE)
+ {
+ /* The following can potentially add EQ_EXPR facts, which could lead
+ to ECs being merged, which would change the meaning of the EC IDs.
+ Hence we need to do this via representatives. */
+ svalue_id lhs = lhs_id.get_obj (*this).get_representative ();
+ svalue_id rhs = rhs_id.get_obj (*this).get_representative ();
+
+ /* We have LHS </<= RHS */
+
+ /* Handle transitivity of ordering by adding additional constraints
+ based on what we already knew.
+
+ So if we have already have:
+ (a < b)
+ (c < d)
+ Then adding:
+ (b < c)
+ will also add:
+ (a < c)
+ (b < d)
+ We need to recurse to ensure we also add:
+ (a < d).
+ We call the checked add_constraint to avoid adding constraints
+ that are already present. Doing so also ensures termination
+ in the case of cycles.
+
+ We also check for single-element ranges, adding EQ_EXPR facts
+ where we discover them. For example 3 < x < 5 implies
+ that x == 4 (if x is an integer). */
+ for (unsigned i = 0; i < m_constraints.length (); i++)
+ {
+ const constraint *other = &m_constraints[i];
+ if (other->is_ordering_p ())
+ {
+ /* Refresh the EC IDs, in case any mergers have happened. */
+ lhs_id = get_or_add_equiv_class (lhs);
+ rhs_id = get_or_add_equiv_class (rhs);
+
+ tree lhs_const = lhs_id.get_obj (*this).m_constant;
+ tree rhs_const = rhs_id.get_obj (*this).m_constant;
+ tree other_lhs_const
+ = other->m_lhs.get_obj (*this).m_constant;
+ tree other_rhs_const
+ = other->m_rhs.get_obj (*this).m_constant;
+
+ /* We have "LHS </<= RHS" and "other.lhs </<= other.rhs". */
+
+ /* If we have LHS </<= RHS and RHS </<= LHS, then we have a
+ cycle. */
+ if (rhs_id == other->m_lhs
+ && other->m_rhs == lhs_id)
+ {
+ /* We must have equality for this to be possible. */
+ gcc_assert (c_op == CONSTRAINT_LE
+ && other->m_op == CONSTRAINT_LE);
+ add_constraint (lhs_id, EQ_EXPR, rhs_id);
+ /* Adding an equality will merge the two ECs and potentially
+ reorganize the constraints. Stop iterating. */
+ return;
+ }
+ /* Otherwise, check for transitivity. */
+ if (rhs_id == other->m_lhs)
+ {
+ /* With RHS == other.lhs, we have:
+ "LHS </<= (RHS, other.lhs) </<= other.rhs"
+ and thus this implies "LHS </<= other.rhs". */
+
+ /* Do we have a tightly-constrained range? */
+ if (lhs_const
+ && !rhs_const
+ && other_rhs_const)
+ {
+ range r (bound (lhs_const, c_op == CONSTRAINT_LE),
+ bound (other_rhs_const,
+ other->m_op == CONSTRAINT_LE));
+ tree constant;
+ if (r.constrained_to_single_element (&constant))
+ {
+ svalue_id cst_sid = get_sid_for_constant (constant);
+ add_constraint
+ (rhs_id, EQ_EXPR,
+ get_or_add_equiv_class (cst_sid));
+ return;
+ }
+ }
+
+ /* Otherwise, add the constraint implied by transitivity. */
+ enum tree_code new_op
+ = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+ ? LE_EXPR : LT_EXPR);
+ add_constraint (lhs_id, new_op, other->m_rhs);
+ }
+ else if (other->m_rhs == lhs_id)
+ {
+ /* With other.rhs == LHS, we have:
+ "other.lhs </<= (other.rhs, LHS) </<= RHS"
+ and thus this implies "other.lhs </<= RHS". */
+
+ /* Do we have a tightly-constrained range? */
+ if (other_lhs_const
+ && !lhs_const
+ && rhs_const)
+ {
+ range r (bound (other_lhs_const,
+ other->m_op == CONSTRAINT_LE),
+ bound (rhs_const,
+ c_op == CONSTRAINT_LE));
+ tree constant;
+ if (r.constrained_to_single_element (&constant))
+ {
+ svalue_id cst_sid = get_sid_for_constant (constant);
+ add_constraint
+ (lhs_id, EQ_EXPR,
+ get_or_add_equiv_class (cst_sid));
+ return;
+ }
+ }
+
+ /* Otherwise, add the constraint implied by transitivity. */
+ enum tree_code new_op
+ = ((c_op == CONSTRAINT_LE && other->m_op == CONSTRAINT_LE)
+ ? LE_EXPR : LT_EXPR);
+ add_constraint (other->m_lhs, new_op, rhs_id);
+ }
+ }
+ }
+ }
+}
+
+/* Look for SID within the equivalence classes of this constraint_manager;
+ if found, write the id to *OUT and return true, otherwise return false. */
+
+bool
+constraint_manager::get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const
+{
+ /* TODO: should we have a map, rather than these searches? */
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ int j;
+ svalue_id *iv;
+ FOR_EACH_VEC_ELT (ec->m_vars, j, iv)
+ if (*iv == sid)
+ {
+ *out = equiv_class_id (i);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Ensure that SID has an equivalence class within this constraint_manager;
+ return the ID of the class. */
+
+equiv_class_id
+constraint_manager::get_or_add_equiv_class (svalue_id sid)
+{
+ equiv_class_id result (-1);
+
+ /* Try svalue_id match. */
+ if (get_equiv_class_by_sid (sid, &result))
+ return result;
+
+ /* Try equality of constants. */
+ if (tree cst = maybe_get_constant (sid))
+ {
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ if (ec->m_constant)
+ {
+ tree eq = fold_build2 (EQ_EXPR, boolean_type_node,
+ cst, ec->m_constant);
+ if (eq == boolean_true_node)
+ {
+ ec->add (sid, *this);
+ return equiv_class_id (i);
+ }
+ }
+ }
+
+
+ /* Not found. */
+ equiv_class *new_ec = new equiv_class ();
+ new_ec->add (sid, *this);
+ m_equiv_classes.safe_push (new_ec);
+
+ equiv_class_id new_id (m_equiv_classes.length () - 1);
+
+ if (maybe_get_constant (sid))
+ {
+ /* If we have a new EC for a constant, add constraints comparing this
+ to other constants we may have (so that we accumulate the transitive
+ closure of all constraints on constants as the constants are
+ added). */
+ for (equiv_class_id other_id (0); other_id.m_idx < new_id.m_idx;
+ other_id.m_idx++)
+ {
+ const equiv_class &other_ec = other_id.get_obj (*this);
+ if (other_ec.m_constant)
+ {
+ /* If we have two ECs, both with constants, the constants must be
+ non-equal (or they would be in the same EC).
+ Determine the direction of the inequality, and record that
+ fact. */
+ tree lt
+ = fold_build2 (LT_EXPR, boolean_type_node,
+ new_ec->m_constant, other_ec.m_constant);
+ //gcc_assert (lt == boolean_true_node || lt == boolean_false_node);
+ // not true for int vs float comparisons
+ if (lt == boolean_true_node)
+ add_constraint_internal (new_id, CONSTRAINT_LT, other_id);
+ else if (lt == boolean_false_node)
+ add_constraint_internal (other_id, CONSTRAINT_LT, new_id);
+ /* Refresh new_id, in case ECs were merged. SID should always
+ be present by now, so this should never lead to a
+ recursion. */
+ new_id = get_or_add_equiv_class (sid);
+ }
+ }
+ }
+
+ return new_id;
+}
+
+/* Evaluate the condition LHS_EC OP RHS_EC. */
+
+tristate
+constraint_manager::eval_condition (equiv_class_id lhs_ec,
+ enum tree_code op,
+ equiv_class_id rhs_ec)
+{
+ if (lhs_ec == rhs_ec)
+ {
+ switch (op)
+ {
+ case EQ_EXPR:
+ case GE_EXPR:
+ case LE_EXPR:
+ return tristate (tristate::TS_TRUE);
+
+ case NE_EXPR:
+ case GT_EXPR:
+ case LT_EXPR:
+ return tristate (tristate::TS_FALSE);
+ default:
+ break;
+ }
+ }
+
+ tree lhs_const = lhs_ec.get_obj (*this).get_any_constant ();
+ tree rhs_const = rhs_ec.get_obj (*this).get_any_constant ();
+ if (lhs_const && rhs_const)
+ {
+ tree comparison
+ = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+ if (comparison == boolean_true_node)
+ return tristate (tristate::TS_TRUE);
+ if (comparison == boolean_false_node)
+ return tristate (tristate::TS_FALSE);
+ }
+
+ enum tree_code swapped_op = swap_tree_comparison (op);
+
+ int i;
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ if (c->m_lhs == lhs_ec
+ && c->m_rhs == rhs_ec)
+ {
+ tristate result_for_constraint
+ = eval_constraint_op_for_op (c->m_op, op);
+ if (result_for_constraint.is_known ())
+ return result_for_constraint;
+ }
+ /* Swapped operands. */
+ if (c->m_lhs == rhs_ec
+ && c->m_rhs == lhs_ec)
+ {
+ tristate result_for_constraint
+ = eval_constraint_op_for_op (c->m_op, swapped_op);
+ if (result_for_constraint.is_known ())
+ return result_for_constraint;
+ }
+ }
+
+ return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Evaluate the condition LHS OP RHS, creating equiv_class instances for
+ LHS and RHS if they aren't already in equiv_classes. */
+
+tristate
+constraint_manager::eval_condition (svalue_id lhs,
+ enum tree_code op,
+ svalue_id rhs)
+{
+ return eval_condition (get_or_add_equiv_class (lhs),
+ op,
+ get_or_add_equiv_class (rhs));
+}
+
+/* Delete any information about svalue_id instances identified by P.
+ Such instances are removed from equivalence classes, and any
+ redundant ECs and constraints are also removed.
+ Accumulate stats into STATS. */
+
+void
+constraint_manager::purge (const purge_criteria &p, purge_stats *stats)
+{
+ /* Delete any svalue_ids identified by P within the various equivalence
+ classes. */
+ for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+ {
+ equiv_class *ec = m_equiv_classes[ec_idx];
+
+ int i;
+ svalue_id *pv;
+ bool delete_ec = false;
+ FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+ {
+ if (*pv == ec->m_cst_sid)
+ continue;
+ if (p.should_purge_p (*pv))
+ {
+ if (ec->del (*pv))
+ if (!ec->m_constant)
+ delete_ec = true;
+ }
+ }
+
+ if (delete_ec)
+ {
+ delete ec;
+ m_equiv_classes.ordered_remove (ec_idx);
+ if (stats)
+ stats->m_num_equiv_classes++;
+
+ /* Update the constraints, potentially removing some. */
+ for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+ {
+ constraint *c = &m_constraints[con_idx];
+
+ /* Remove constraints that refer to the deleted EC. */
+ if (c->m_lhs == ec_idx
+ || c->m_rhs == ec_idx)
+ {
+ m_constraints.ordered_remove (con_idx);
+ if (stats)
+ stats->m_num_constraints++;
+ }
+ else
+ {
+ /* Renumber constraints that refer to ECs that have
+ had their idx changed. */
+ c->m_lhs.update_for_removal (ec_idx);
+ c->m_rhs.update_for_removal (ec_idx);
+
+ con_idx++;
+ }
+ }
+ }
+ else
+ ec_idx++;
+ }
+
+ /* Now delete any constraints that are purely between constants. */
+ for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+ {
+ constraint *c = &m_constraints[con_idx];
+ if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
+ && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
+ {
+ m_constraints.ordered_remove (con_idx);
+ if (stats)
+ stats->m_num_constraints++;
+ }
+ else
+ {
+ con_idx++;
+ }
+ }
+
+ /* Finally, delete any ECs that purely contain constants and aren't
+ referenced by any constraints. */
+ for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+ {
+ equiv_class *ec = m_equiv_classes[ec_idx];
+ if (ec->m_vars.length () == 0)
+ {
+ equiv_class_id ec_id (ec_idx);
+ bool has_constraint = false;
+ for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+ con_idx++)
+ {
+ constraint *c = &m_constraints[con_idx];
+ if (c->m_lhs == ec_id
+ || c->m_rhs == ec_id)
+ {
+ has_constraint = true;
+ break;
+ }
+ }
+ if (!has_constraint)
+ {
+ delete ec;
+ m_equiv_classes.ordered_remove (ec_idx);
+ if (stats)
+ stats->m_num_equiv_classes++;
+
+ /* Renumber constraints that refer to ECs that have
+ had their idx changed. */
+ for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+ con_idx++)
+ {
+ constraint *c = &m_constraints[con_idx];
+ c->m_lhs.update_for_removal (ec_idx);
+ c->m_rhs.update_for_removal (ec_idx);
+ }
+ continue;
+ }
+ }
+ ec_idx++;
+ }
+
+ validate ();
+}
+
+/* Remap all svalue_ids within this constraint_manager using MAP. */
+
+void
+constraint_manager::remap_svalue_ids (const svalue_id_map &map)
+{
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ ec->remap_svalue_ids (map);
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+ Sort a pair of equiv_class instances, using the representative
+ svalue_id as a sort key. */
+
+static int
+equiv_class_cmp (const void *p1, const void *p2)
+{
+ const equiv_class *ec1 = *(const equiv_class * const *)p1;
+ const equiv_class *ec2 = *(const equiv_class * const *)p2;
+
+ svalue_id rep1 = ec1->get_representative ();
+ svalue_id rep2 = ec2->get_representative ();
+
+ return rep1.as_int () - rep2.as_int ();
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+ Sort a pair of constraint instances. */
+
+static int
+constraint_cmp (const void *p1, const void *p2)
+{
+ const constraint *c1 = (const constraint *)p1;
+ const constraint *c2 = (const constraint *)p2;
+ int lhs_cmp = c1->m_lhs.as_int () - c2->m_lhs.as_int ();
+ if (lhs_cmp)
+ return lhs_cmp;
+ int rhs_cmp = c1->m_rhs.as_int () - c2->m_rhs.as_int ();
+ if (rhs_cmp)
+ return rhs_cmp;
+ return c1->m_op - c2->m_op;
+}
+
+/* Reorder the equivalence classes and constraints within this
+ constraint_manager into a canonical order, to increase the
+ chances of finding equality with another instance. */
+
+void
+constraint_manager::canonicalize (unsigned num_svalue_ids)
+{
+ /* First, sort svalue_ids within the ECs. */
+ unsigned i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ ec->canonicalize ();
+
+ /* Next, sort the ECs into a canonical order. */
+
+ /* We will need to remap the equiv_class_ids in the constraints,
+ so we need to store the original index of each EC.
+ Build a lookup table, mapping from representative svalue_id
+ to the original equiv_class_id of that svalue_id. */
+ auto_vec<equiv_class_id> original_ec_id (num_svalue_ids);
+ for (i = 0; i < num_svalue_ids; i++)
+ original_ec_id.quick_push (equiv_class_id::null ());
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ svalue_id rep = ec->get_representative ();
+ gcc_assert (!rep.null_p ());
+ original_ec_id[rep.as_int ()] = i;
+ }
+
+ /* Sort the equivalence classes. */
+ m_equiv_classes.qsort (equiv_class_cmp);
+
+ /* Populate ec_id_map based on the old vs new EC ids. */
+ one_way_id_map<equiv_class_id> ec_id_map (m_equiv_classes.length ());
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ svalue_id rep = ec->get_representative ();
+ ec_id_map.put (original_ec_id[rep.as_int ()], i);
+ }
+
+ /* Update the EC ids within the constraints. */
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ ec_id_map.update (&c->m_lhs);
+ ec_id_map.update (&c->m_rhs);
+ }
+
+ /* Finally, sort the constraints. */
+ m_constraints.qsort (constraint_cmp);
+}
+
+/* A concrete subclass of constraint_manager for use when
+ merging two constraint_manager into a third constraint_manager,
+ each of which has its own region_model.
+ Calls are delegated to the constraint_manager for the merged model,
+ and thus affect its region_model. */
+
+class cleaned_constraint_manager : public constraint_manager
+{
+public:
+ cleaned_constraint_manager (constraint_manager *merged) : m_merged (merged) {}
+
+ constraint_manager *clone (region_model *) const FINAL OVERRIDE
+ {
+ gcc_unreachable ();
+ }
+ tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+ {
+ return m_merged->maybe_get_constant (sid);
+ }
+ svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+ {
+ return m_merged->get_sid_for_constant (cst);
+ }
+ virtual int get_num_svalues () const FINAL OVERRIDE
+ {
+ return m_merged->get_num_svalues ();
+ }
+private:
+ constraint_manager *m_merged;
+};
+
+/* Concrete subclass of fact_visitor for use by constraint_manager::merge.
+ For every fact in CM_A, see if it is also true in *CM_B. Add such
+ facts to *OUT. */
+
+class merger_fact_visitor : public fact_visitor
+{
+public:
+ merger_fact_visitor (constraint_manager *cm_b,
+ constraint_manager *out)
+ : m_cm_b (cm_b), m_out (out)
+ {}
+
+ void on_fact (svalue_id lhs, enum tree_code code, svalue_id rhs)
+ FINAL OVERRIDE
+ {
+ if (m_cm_b->eval_condition (lhs, code, rhs).is_true ())
+ {
+ bool sat = m_out->add_constraint (lhs, code, rhs);
+ gcc_assert (sat);
+ }
+ }
+
+private:
+ constraint_manager *m_cm_b;
+ constraint_manager *m_out;
+};
+
+/* Use MERGER to merge CM_A and CM_B into *OUT.
+ If one thinks of a constraint_manager as a subset of N-dimensional
+ space, this takes the union of the points of CM_A and CM_B, and
+ expresses that into *OUT. Alternatively, it can be thought of
+ as the intersection of the constraints. */
+
+void
+constraint_manager::merge (const constraint_manager &cm_a,
+ const constraint_manager &cm_b,
+ constraint_manager *out,
+ const model_merger &merger)
+{
+ gcc_assert (merger.m_sid_mapping);
+
+ /* Map svalue_ids in each equiv class from both sources
+ to the merged region_model, dropping ids that don't survive merger,
+ and potentially creating svalues in *OUT for constants. */
+ cleaned_constraint_manager cleaned_cm_a (out);
+ const one_way_svalue_id_map &map_a_to_m
+ = merger.m_sid_mapping->m_map_from_a_to_m;
+ clean_merger_input (cm_a, map_a_to_m, &cleaned_cm_a);
+
+ cleaned_constraint_manager cleaned_cm_b (out);
+ const one_way_svalue_id_map &map_b_to_m
+ = merger.m_sid_mapping->m_map_from_b_to_m;
+ clean_merger_input (cm_b, map_b_to_m, &cleaned_cm_b);
+
+ /* At this point, the two cleaned CMs have ECs and constraints referring
+ to svalues in the merged region model, but both of them have separate
+ ECs. */
+
+ /* Merge the equivalence classes and constraints.
+ The easiest way to do this seems to be to enumerate all of the facts
+ in cleaned_cm_a, see which are also true in cleaned_cm_b,
+ and add those to *OUT. */
+ merger_fact_visitor v (&cleaned_cm_b, out);
+ cleaned_cm_a.for_each_fact (&v);
+}
+
+/* A subroutine of constraint_manager::merge.
+ Use MAP_SID_TO_M to map equivalence classes and constraints from
+ SM_IN to *OUT. Purge any non-constant svalue_id that don't appear
+ in the result of MAP_SID_TO_M, purging any ECs and their constraints
+ that become empty as a result. Potentially create svalues in
+ the merged region_model for constants that weren't already in use there. */
+
+void
+constraint_manager::
+clean_merger_input (const constraint_manager &cm_in,
+ const one_way_svalue_id_map &map_sid_to_m,
+ constraint_manager *out)
+{
+ one_way_id_map<equiv_class_id> map_ec_to_m
+ (cm_in.m_equiv_classes.length ());
+ unsigned ec_idx;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (cm_in.m_equiv_classes, ec_idx, ec)
+ {
+ equiv_class cleaned_ec;
+ if (tree cst = ec->get_any_constant ())
+ {
+ cleaned_ec.m_constant = cst;
+ /* Lazily create the constant in the out region_model. */
+ cleaned_ec.m_cst_sid = out->get_sid_for_constant (cst);
+ }
+ unsigned var_idx;
+ svalue_id *var_in_sid;
+ FOR_EACH_VEC_ELT (ec->m_vars, var_idx, var_in_sid)
+ {
+ svalue_id var_m_sid = map_sid_to_m.get_dst_for_src (*var_in_sid);
+ if (!var_m_sid.null_p ())
+ cleaned_ec.m_vars.safe_push (var_m_sid);
+ }
+ if (cleaned_ec.get_any_constant () || !cleaned_ec.m_vars.is_empty ())
+ {
+ map_ec_to_m.put (ec_idx, out->m_equiv_classes.length ());
+ out->m_equiv_classes.safe_push (new equiv_class (cleaned_ec));
+ }
+ }
+
+ /* Write out to *OUT any constraints for which both sides survived
+ cleaning, using the new EC IDs. */
+ unsigned con_idx;
+ constraint *c;
+ FOR_EACH_VEC_ELT (cm_in.m_constraints, con_idx, c)
+ {
+ equiv_class_id new_lhs = map_ec_to_m.get_dst_for_src (c->m_lhs);
+ if (new_lhs.null_p ())
+ continue;
+ equiv_class_id new_rhs = map_ec_to_m.get_dst_for_src (c->m_rhs);
+ if (new_rhs.null_p ())
+ continue;
+ out->m_constraints.safe_push (constraint (new_lhs,
+ c->m_op,
+ new_rhs));
+ }
+}
+
+/* Call VISITOR's on_fact vfunc repeatedly to express the various
+ equivalence classes and constraints.
+ This is used by constraint_manager::merge to find the common
+ facts between two input constraint_managers. */
+
+void
+constraint_manager::for_each_fact (fact_visitor *visitor) const
+{
+ /* First, call EQ_EXPR within the various equivalence classes. */
+ unsigned ec_idx;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, ec_idx, ec)
+ {
+ if (!ec->m_cst_sid.null_p ())
+ {
+ unsigned i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (ec->m_vars, i, sid)
+ visitor->on_fact (ec->m_cst_sid, EQ_EXPR, *sid);
+ }
+ for (unsigned i = 0; i < ec->m_vars.length (); i++)
+ for (unsigned j = i + 1; j < ec->m_vars.length (); j++)
+ visitor->on_fact (ec->m_vars[i], EQ_EXPR, ec->m_vars[j]);
+ }
+
+ /* Now, express the various constraints. */
+ unsigned con_idx;
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, con_idx, c)
+ {
+ const equiv_class &ec_lhs = c->m_lhs.get_obj (*this);
+ const equiv_class &ec_rhs = c->m_rhs.get_obj (*this);
+ enum tree_code code = constraint_tree_code (c->m_op);
+
+ if (!ec_lhs.m_cst_sid.null_p ())
+ {
+ for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+ {
+ visitor->on_fact (ec_lhs.m_cst_sid, code, ec_rhs.m_vars[j]);
+ }
+ }
+ for (unsigned i = 0; i < ec_lhs.m_vars.length (); i++)
+ {
+ if (!ec_rhs.m_cst_sid.null_p ())
+ visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_cst_sid);
+ for (unsigned j = 0; j < ec_rhs.m_vars.length (); j++)
+ visitor->on_fact (ec_lhs.m_vars[i], code, ec_rhs.m_vars[j]);
+ }
+ }
+}
+
+/* Assert that this object is valid. */
+
+void
+constraint_manager::validate () const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ int i;
+ equiv_class *ec;
+ FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+ {
+ gcc_assert (ec);
+
+ int j;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+ {
+ gcc_assert (!sid->null_p ());
+ gcc_assert (sid->as_int () < get_num_svalues ());
+ }
+ if (ec->m_constant)
+ gcc_assert (CONSTANT_CLASS_P (ec->m_constant));
+#if 0
+ else
+ gcc_assert (ec->m_vars.length () > 0);
+#endif
+ }
+
+ constraint *c;
+ FOR_EACH_VEC_ELT (m_constraints, i, c)
+ {
+ gcc_assert (!c->m_lhs.null_p ());
+ gcc_assert (c->m_lhs.as_int () <= (int)m_equiv_classes.length ());
+ gcc_assert (!c->m_rhs.null_p ());
+ gcc_assert (c->m_rhs.as_int () <= (int)m_equiv_classes.length ());
+ }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Various constraint_manager selftests.
+ These have to be written in terms of a region_model, since
+ the latter is responsible for managing svalue and svalue_id
+ instances. */
+
+/* Verify that setting and getting simple conditions within a region_model
+ work (thus exercising the underlying constraint_manager). */
+
+static void
+test_constraint_conditions ()
+{
+ tree int_42 = build_int_cst (integer_type_node, 42);
+ tree int_0 = build_int_cst (integer_type_node, 0);
+
+ tree x = build_global_decl ("x", integer_type_node);
+ tree y = build_global_decl ("y", integer_type_node);
+ tree z = build_global_decl ("z", integer_type_node);
+
+ /* Self-comparisons. */
+ {
+ region_model model;
+ ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, x, NE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, x, LT_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, x);
+ }
+
+ /* x == y. */
+ {
+ region_model model;
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+ ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+
+ /* Comparison with other var. */
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+ }
+
+ /* x == y, then y == z */
+ {
+ region_model model;
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+ ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, z);
+
+ ASSERT_CONDITION_TRUE (model, x, EQ_EXPR, z);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, z);
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+ ASSERT_CONDITION_FALSE (model, x, NE_EXPR, z);
+ ASSERT_CONDITION_FALSE (model, x, LT_EXPR, z);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, z);
+ }
+
+ /* x != y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+
+ ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+
+ /* Comparison with other var. */
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, z);
+ }
+
+ /* x < y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, LT_EXPR, y);
+
+ ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, LE_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, GT_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+ }
+
+ /* x <= y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+
+ ASSERT_CONDITION_UNKNOWN (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_FALSE (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, LE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, GT_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, GE_EXPR, x);
+ }
+
+ /* x > y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, GT_EXPR, y);
+
+ ASSERT_CONDITION_TRUE (model, x, GT_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, LE_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, GE_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_FALSE (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+ }
+
+ /* x >= y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, GE_EXPR, y);
+
+ ASSERT_CONDITION_UNKNOWN (model, x, GT_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, LE_EXPR, y);
+
+ /* Swapped operands. */
+ ASSERT_CONDITION_FALSE (model, y, GT_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, GE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, NE_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, x);
+ ASSERT_CONDITION_UNKNOWN (model, y, LT_EXPR, x);
+ ASSERT_CONDITION_TRUE (model, y, LE_EXPR, x);
+ }
+
+ // TODO: implied orderings
+
+ /* Constants. */
+ {
+ region_model model;
+ ASSERT_CONDITION_FALSE (model, int_0, EQ_EXPR, int_42);
+ ASSERT_CONDITION_TRUE (model, int_0, NE_EXPR, int_42);
+ ASSERT_CONDITION_TRUE (model, int_0, LT_EXPR, int_42);
+ ASSERT_CONDITION_TRUE (model, int_0, LE_EXPR, int_42);
+ ASSERT_CONDITION_FALSE (model, int_0, GT_EXPR, int_42);
+ ASSERT_CONDITION_FALSE (model, int_0, GE_EXPR, int_42);
+ }
+
+ /* x == 0, y == 42. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+ ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, int_42);
+
+ ASSERT_CONDITION_TRUE (model, x, NE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, LE_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GE_EXPR, y);
+ ASSERT_CONDITION_TRUE (model, x, LT_EXPR, y);
+ ASSERT_CONDITION_FALSE (model, x, GT_EXPR, y);
+ }
+
+ /* Unsatisfiable combinations. */
+
+ /* x == y && x != y. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+ ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, y);
+ }
+
+ /* x == 0 then x == 42. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+ ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, int_42);
+ }
+
+ /* x == 0 then x != 0. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+ ADD_UNSAT_CONSTRAINT (model, x, NE_EXPR, int_0);
+ }
+
+ /* x == 0 then x > 0. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+ ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, int_0);
+ }
+
+ /* x != y && x == y. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, NE_EXPR, y);
+ ADD_UNSAT_CONSTRAINT (model, x, EQ_EXPR, y);
+ }
+
+ /* x <= y && x > y. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, x, LE_EXPR, y);
+ ADD_UNSAT_CONSTRAINT (model, x, GT_EXPR, y);
+ }
+
+ // etc
+}
+
+/* Test transitivity of conditions. */
+
+static void
+test_transitivity ()
+{
+ tree a = build_global_decl ("a", integer_type_node);
+ tree b = build_global_decl ("b", integer_type_node);
+ tree c = build_global_decl ("c", integer_type_node);
+ tree d = build_global_decl ("d", integer_type_node);
+
+ /* a == b, then c == d, then c == b. */
+ {
+ region_model model;
+ ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, b);
+ ASSERT_CONDITION_UNKNOWN (model, b, EQ_EXPR, c);
+ ASSERT_CONDITION_UNKNOWN (model, c, EQ_EXPR, d);
+ ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+ ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, b);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+
+ ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, d);
+ ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, d);
+ ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, d);
+
+ ADD_SAT_CONSTRAINT (model, c, EQ_EXPR, b);
+ ASSERT_CONDITION_TRUE (model, c, EQ_EXPR, b);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, d);
+ }
+
+ /* Transitivity: "a < b", "b < c" should imply "a < c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+ ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a <= b", "b < c" should imply "a < c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+ ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a <= b", "b <= c" should imply "a <= c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, LE_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, LE_EXPR, c);
+ ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a > b", "b > c" should imply "a > c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+ ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a >= b", "b > c" should imply " a > c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, GT_EXPR, c);
+ ASSERT_CONDITION_FALSE (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a >= b", "b >= c" should imply "a >= c". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, GE_EXPR, c);
+ ASSERT_CONDITION_UNKNOWN (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "(a < b)", "(c < d)", "(b < c)" should
+ imply the easy cases:
+ (a < c)
+ (b < d)
+ but also that:
+ (a < d). */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LT_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, c, LT_EXPR, d);
+ ADD_SAT_CONSTRAINT (model, b, LT_EXPR, c);
+
+ ASSERT_CONDITION_TRUE (model, a, LT_EXPR, c);
+ ASSERT_CONDITION_TRUE (model, b, LT_EXPR, d);
+ ASSERT_CONDITION_TRUE (model, a, LT_EXPR, d);
+ }
+
+ /* Transitivity: "a >= b", "b >= a" should imply that a == b. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GE_EXPR, a);
+
+ // TODO:
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, b);
+ }
+
+ /* Transitivity: "a >= b", "b > a" should be impossible. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+ ADD_UNSAT_CONSTRAINT (model, b, GT_EXPR, a);
+ }
+
+ /* Transitivity: "a >= b", "b >= c", "c >= a" should imply
+ that a == b == c. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GE_EXPR, c);
+ ADD_SAT_CONSTRAINT (model, c, GE_EXPR, a);
+
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, c);
+ }
+
+ /* Transitivity: "a > b", "b > c", "c > a"
+ should be impossible. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, b, GT_EXPR, c);
+ ADD_UNSAT_CONSTRAINT (model, c, GT_EXPR, a);
+ }
+
+}
+
+/* Test various conditionals involving constants where the results
+ ought to be implied based on the values of the constants. */
+
+static void
+test_constant_comparisons ()
+{
+ tree int_3 = build_int_cst (integer_type_node, 3);
+ tree int_4 = build_int_cst (integer_type_node, 4);
+ tree int_5 = build_int_cst (integer_type_node, 5);
+
+ tree int_1023 = build_int_cst (integer_type_node, 1023);
+ tree int_1024 = build_int_cst (integer_type_node, 1024);
+
+ tree a = build_global_decl ("a", integer_type_node);
+ tree b = build_global_decl ("b", integer_type_node);
+
+ /* Given a >= 1024, then a <= 1023 should be impossible. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_1024);
+ ADD_UNSAT_CONSTRAINT (model, a, LE_EXPR, int_1023);
+ }
+
+ /* a > 4. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_4);
+ ASSERT_CONDITION_TRUE (model, a, GT_EXPR, int_4);
+ ASSERT_CONDITION_TRUE (model, a, NE_EXPR, int_3);
+ ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_5);
+ }
+
+ /* a <= 4. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+ ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_4);
+ ASSERT_CONDITION_FALSE (model, a, GT_EXPR, int_5);
+ ASSERT_CONDITION_UNKNOWN (model, a, NE_EXPR, int_3);
+ }
+
+ /* If "a > b" and "a == 3", then "b == 4" ought to be unsatisfiable. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, b);
+ ADD_SAT_CONSTRAINT (model, a, EQ_EXPR, int_3);
+ ADD_UNSAT_CONSTRAINT (model, b, EQ_EXPR, int_4);
+ }
+
+ /* Various tests of int ranges where there is only one possible candidate. */
+ {
+ /* If "a <= 4" && "a > 3", then "a == 4",
+ assuming a is of integral type. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+ }
+
+ /* If "a > 3" && "a <= 4", then "a == 4",
+ assuming a is of integral type. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+ }
+ /* If "a > 3" && "a < 5", then "a == 4",
+ assuming a is of integral type. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GT_EXPR, int_3);
+ ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+ }
+ /* If "a >= 4" && "a < 5", then "a == 4",
+ assuming a is of integral type. */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+ ADD_SAT_CONSTRAINT (model, a, LT_EXPR, int_5);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+ }
+ /* If "a >= 4" && "a <= 4", then "a == 4". */
+ {
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, a, GE_EXPR, int_4);
+ ADD_SAT_CONSTRAINT (model, a, LE_EXPR, int_4);
+ ASSERT_CONDITION_TRUE (model, a, EQ_EXPR, int_4);
+ }
+ }
+
+ /* As above, but for floating-point:
+ if "f > 3" && "f <= 4" we don't know that f == 4. */
+ {
+ tree f = build_global_decl ("f", double_type_node);
+ tree float_3 = build_real_from_int_cst (double_type_node, int_3);
+ tree float_4 = build_real_from_int_cst (double_type_node, int_4);
+
+ region_model model;
+ ADD_SAT_CONSTRAINT (model, f, GT_EXPR, float_3);
+ ADD_SAT_CONSTRAINT (model, f, LE_EXPR, float_4);
+ ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, float_4);
+ ASSERT_CONDITION_UNKNOWN (model, f, EQ_EXPR, int_4);
+ }
+}
+
+/* Verify various lower-level implementation details about
+ constraint_manager. */
+
+static void
+test_constraint_impl ()
+{
+ tree int_42 = build_int_cst (integer_type_node, 42);
+ tree int_0 = build_int_cst (integer_type_node, 0);
+
+ tree x = build_global_decl ("x", integer_type_node);
+ tree y = build_global_decl ("y", integer_type_node);
+ tree z = build_global_decl ("z", integer_type_node);
+
+ /* x == y. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+ /* Assert various things about the insides of model. */
+ constraint_manager *cm = model.get_constraints ();
+ ASSERT_EQ (cm->m_constraints.length (), 0);
+ ASSERT_EQ (cm->m_equiv_classes.length (), 1);
+ }
+
+ /* y <= z; x == y. */
+ {
+ region_model model;
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+ ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+ ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, y);
+
+ /* Assert various things about the insides of model. */
+ constraint_manager *cm = model.get_constraints ();
+ ASSERT_EQ (cm->m_constraints.length (), 1);
+ ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+ /* Ensure that we merged the constraints. */
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+ }
+
+ /* y <= z; y == x. */
+ {
+ region_model model;
+ ASSERT_CONDITION_UNKNOWN (model, x, EQ_EXPR, y);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+ ADD_SAT_CONSTRAINT (model, y, GE_EXPR, z);
+ ASSERT_CONDITION_TRUE (model, y, GE_EXPR, z);
+ ASSERT_CONDITION_UNKNOWN (model, x, GE_EXPR, z);
+
+ ADD_SAT_CONSTRAINT (model, y, EQ_EXPR, x);
+
+ /* Assert various things about the insides of model. */
+ constraint_manager *cm = model.get_constraints ();
+ ASSERT_EQ (cm->m_constraints.length (), 1);
+ ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+
+ /* Ensure that we merged the constraints. */
+ ASSERT_CONDITION_TRUE (model, x, GE_EXPR, z);
+ }
+
+ /* x == 0, then x != 42. */
+ {
+ region_model model;
+
+ ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+ ADD_SAT_CONSTRAINT (model, x, NE_EXPR, int_42);
+
+ /* Assert various things about the insides of model. */
+ constraint_manager *cm = model.get_constraints ();
+ ASSERT_EQ (cm->m_constraints.length (), 1);
+ ASSERT_EQ (cm->m_equiv_classes.length (), 2);
+ ASSERT_EQ (cm->m_constraints[0].m_lhs,
+ cm->get_or_add_equiv_class (model.get_rvalue (int_0, NULL)));
+ ASSERT_EQ (cm->m_constraints[0].m_rhs,
+ cm->get_or_add_equiv_class (model.get_rvalue (int_42, NULL)));
+ ASSERT_EQ (cm->m_constraints[0].m_op, CONSTRAINT_LT);
+ }
+
+ // TODO: selftest for merging ecs "in the middle"
+ // where a non-final one gets overwritten
+
+ // TODO: selftest where there are pre-existing constraints
+}
+
+/* Check that operator== and hashing works as expected for the
+ various types. */
+
+static void
+test_equality ()
+{
+ tree x = build_global_decl ("x", integer_type_node);
+ tree y = build_global_decl ("y", integer_type_node);
+
+ {
+ region_model model0;
+ region_model model1;
+
+ constraint_manager *cm0 = model0.get_constraints ();
+ constraint_manager *cm1 = model1.get_constraints ();
+
+ ASSERT_EQ (cm0->hash (), cm1->hash ());
+ ASSERT_EQ (*cm0, *cm1);
+
+ ASSERT_EQ (model0.hash (), model1.hash ());
+ ASSERT_EQ (model0, model1);
+
+ ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+ ASSERT_NE (cm0->hash (), cm1->hash ());
+ ASSERT_NE (*cm0, *cm1);
+
+ ASSERT_NE (model0.hash (), model1.hash ());
+ ASSERT_NE (model0, model1);
+
+ region_model model2;
+ constraint_manager *cm2 = model2.get_constraints ();
+ /* Make the same change to cm2. */
+ ADD_SAT_CONSTRAINT (model2, x, EQ_EXPR, y);
+ ASSERT_EQ (cm1->hash (), cm2->hash ());
+ ASSERT_EQ (*cm1, *cm2);
+
+ ASSERT_EQ (model1.hash (), model2.hash ());
+ ASSERT_EQ (model1, model2);
+ }
+}
+
+/* Verify tracking inequality of a variable against many constants. */
+
+static void
+test_many_constants ()
+{
+ tree a = build_global_decl ("a", integer_type_node);
+
+ region_model model;
+ auto_vec<tree> constants;
+ for (int i = 0; i < 20; i++)
+ {
+ tree constant = build_int_cst (integer_type_node, i);
+ constants.safe_push (constant);
+ ADD_SAT_CONSTRAINT (model, a, NE_EXPR, constant);
+
+ /* Merge, and check the result. */
+ region_model other (model);
+
+ region_model merged;
+ ASSERT_TRUE (model.can_merge_with_p (other, &merged));
+ model.canonicalize (NULL);
+ merged.canonicalize (NULL);
+ ASSERT_EQ (model, merged);
+
+ for (int j = 0; j <= i; j++)
+ ASSERT_CONDITION_TRUE (model, a, NE_EXPR, constants[j]);
+ }
+}
+
+/* Run the selftests in this file, temporarily overriding
+ flag_analyzer_transitivity with TRANSITIVITY. */
+
+static void
+run_constraint_manager_tests (bool transitivity)
+{
+ int saved_flag_analyzer_transitivity = flag_analyzer_transitivity;
+ flag_analyzer_transitivity = transitivity;
+
+ test_constraint_conditions ();
+ if (flag_analyzer_transitivity)
+ {
+ /* These selftests assume transitivity. */
+ test_transitivity ();
+ test_constant_comparisons ();
+ }
+ test_constraint_impl ();
+ test_equality ();
+ test_many_constants ();
+
+ flag_analyzer_transitivity = saved_flag_analyzer_transitivity;
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_constraint_manager_cc_tests ()
+{
+ /* Run the tests twice: with and without transitivity. */
+ run_constraint_manager_tests (true);
+ run_constraint_manager_tests (false);
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
new file mode 100644
index 00000000000..c6a40f90a33
--- /dev/null
+++ b/gcc/analyzer/constraint-manager.h
@@ -0,0 +1,246 @@
+/* Tracking equivalence classes and constraints at a point on an execution path.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_CONSTRAINT_MANAGER_H
+#define GCC_ANALYZER_CONSTRAINT_MANAGER_H
+
+class constraint_manager;
+
+/* Abstract base class for specifying how state should be purged. */
+
+class purge_criteria
+{
+public:
+ virtual ~purge_criteria () {}
+ virtual bool should_purge_p (svalue_id sid) const = 0;
+};
+
+/* An equivalence class within a constraint manager: a set of
+ svalue_ids that are known to all be equal to each other,
+ together with an optional tree constant that they are equal to. */
+
+class equiv_class
+{
+public:
+ equiv_class ();
+ equiv_class (const equiv_class &other);
+
+ hashval_t hash () const;
+ bool operator== (const equiv_class &other);
+
+ void add (svalue_id sid, const constraint_manager &cm);
+ bool del (svalue_id sid);
+
+ tree get_any_constant () const { return m_constant; }
+
+ svalue_id get_representative () const;
+
+ void remap_svalue_ids (const svalue_id_map &map);
+
+ void canonicalize ();
+
+ void print (pretty_printer *pp) const;
+
+ /* An equivalence class can contain multiple constants (e.g. multiple
+ different zeroes, for different types); these are just for the last
+ constant added. */
+ tree m_constant;
+ svalue_id m_cst_sid;
+
+ // TODO: should this be a set rather than a vec?
+ auto_vec<svalue_id> m_vars;
+};
+
+/* The various kinds of constraint. */
+
+enum constraint_op
+{
+ CONSTRAINT_NE,
+ CONSTRAINT_LT,
+ CONSTRAINT_LE
+};
+
+const char *constraint_op_code (enum constraint_op c_op);
+
+/* An ID for an equiv_class within a constraint_manager. Internally, this
+ is an index into a vector of equiv_class * within the constraint_manager. */
+
+class equiv_class_id
+{
+public:
+ static equiv_class_id null () { return equiv_class_id (-1); }
+
+ equiv_class_id (unsigned idx) : m_idx (idx) {}
+ const equiv_class &get_obj (const constraint_manager &cm) const;
+ equiv_class &get_obj (constraint_manager &cm) const;
+
+ bool operator== (const equiv_class_id &other) const
+ {
+ return m_idx == other.m_idx;
+ }
+ bool operator!= (const equiv_class_id &other) const
+ {
+ return m_idx != other.m_idx;
+ }
+
+ bool null_p () const { return m_idx == -1; }
+
+ static equiv_class_id from_int (int idx) { return equiv_class_id (idx); }
+ int as_int () const { return m_idx; }
+
+ void print (pretty_printer *pp) const;
+
+ void update_for_removal (equiv_class_id other)
+ {
+ if (m_idx > other.m_idx)
+ m_idx--;
+ }
+
+ int m_idx;
+};
+
+/* A relationship between two equivalence classes in a constraint_manager. */
+
+class constraint
+{
+ public:
+ constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs)
+ : m_lhs (lhs), m_op (c_op), m_rhs (rhs)
+ {
+ gcc_assert (!lhs.null_p ());
+ gcc_assert (!rhs.null_p ());
+ }
+
+ void print (pretty_printer *pp, const constraint_manager &cm) const;
+
+ hashval_t hash () const;
+ bool operator== (const constraint &other) const;
+
+ /* Is this an ordering, rather than a "!=". */
+ bool is_ordering_p () const
+ {
+ return m_op != CONSTRAINT_NE;
+ }
+
+ equiv_class_id m_lhs;
+ enum constraint_op m_op;
+ equiv_class_id m_rhs;
+};
+
+/* An abstract base class for use with constraint_manager::for_each_fact. */
+
+class fact_visitor
+{
+ public:
+ virtual ~fact_visitor () {}
+ virtual void on_fact (svalue_id lhs, enum tree_code, svalue_id rhs) = 0;
+};
+
+/* A collection of equivalence classes and constraints on them.
+
+ Given N svalues, this can be thought of as representing a subset of
+ N-dimensional space. When we call add_constraint,
+ we are effectively taking an intersection with that constraint. */
+
+class constraint_manager
+{
+public:
+ constraint_manager () {}
+ constraint_manager (const constraint_manager &other);
+ virtual ~constraint_manager () {}
+
+ virtual constraint_manager *clone (region_model *) const = 0;
+ virtual tree maybe_get_constant (svalue_id sid) const = 0;
+ virtual svalue_id get_sid_for_constant (tree cst) const = 0;
+ virtual int get_num_svalues () const = 0;
+
+ constraint_manager& operator= (const constraint_manager &other);
+
+ hashval_t hash () const;
+ bool operator== (const constraint_manager &other) const;
+ bool operator!= (const constraint_manager &other) const
+ {
+ return !(*this == other);
+ }
+
+ void print (pretty_printer *pp) const;
+ void dump_to_pp (pretty_printer *pp) const;
+ void dump (FILE *fp) const;
+ void dump () const;
+
+ const equiv_class &get_equiv_class_by_index (unsigned idx) const
+ {
+ return *m_equiv_classes[idx];
+ }
+ equiv_class &get_equiv_class_by_index (unsigned idx)
+ {
+ return *m_equiv_classes[idx];
+ }
+
+ equiv_class &get_equiv_class (svalue_id sid)
+ {
+ equiv_class_id ec_id = get_or_add_equiv_class (sid);
+ return ec_id.get_obj (*this);
+ }
+
+ bool add_constraint (svalue_id lhs, enum tree_code op, svalue_id rhs);
+
+ bool add_constraint (equiv_class_id lhs_ec_id,
+ enum tree_code op,
+ equiv_class_id rhs_ec_id);
+
+ bool get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const;
+ equiv_class_id get_or_add_equiv_class (svalue_id sid);
+ tristate eval_condition (equiv_class_id lhs,
+ enum tree_code op,
+ equiv_class_id rhs);
+ tristate eval_condition (svalue_id lhs,
+ enum tree_code op,
+ svalue_id rhs);
+
+ void purge (const purge_criteria &p, purge_stats *stats);
+
+ void remap_svalue_ids (const svalue_id_map &map);
+
+ void canonicalize (unsigned num_svalue_ids);
+
+ static void merge (const constraint_manager &cm_a,
+ const constraint_manager &cm_b,
+ constraint_manager *out,
+ const model_merger &merger);
+
+ void for_each_fact (fact_visitor *) const;
+
+ void validate () const;
+
+ auto_delete_vec<equiv_class> m_equiv_classes;
+ auto_vec<constraint> m_constraints;
+
+ private:
+ static void clean_merger_input (const constraint_manager &cm_in,
+ const one_way_svalue_id_map &map_sid_to_m,
+ constraint_manager *out);
+
+ void add_constraint_internal (equiv_class_id lhs_id,
+ enum constraint_op c_op,
+ equiv_class_id rhs_id);
+};
+
+#endif /* GCC_ANALYZER_CONSTRAINT_MANAGER_H */
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
new file mode 100644
index 00000000000..cd2c3bf2076
--- /dev/null
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -0,0 +1,1243 @@
+/* Classes for saving, deduplicating, and emitting analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "pretty-print.h"
+#include "gcc-rich-location.h"
+#include "gimple-pretty-print.h"
+#include "function.h"
+#include "diagnostic-core.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-path.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "ordered-hash-map.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/checker-path.h"
+
+#if ENABLE_ANALYZER
+
+/* class saved_diagnostic. */
+
+/* saved_diagnostic's ctor.
+ Take ownership of D and STMT_FINDER. */
+
+saved_diagnostic::saved_diagnostic (const state_machine *sm,
+ const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *stmt_finder,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d)
+: m_sm (sm), m_enode (enode), m_snode (snode), m_stmt (stmt),
+ /* stmt_finder could be on-stack; we want our own copy that can
+ outlive that. */
+ m_stmt_finder (stmt_finder ? stmt_finder->clone () : NULL),
+ m_var (var), m_state (state),
+ m_d (d), m_trailing_eedge (NULL)
+{
+ gcc_assert (m_stmt || m_stmt_finder);
+
+ /* We must have an enode in order to be able to look for paths
+ through the exploded_graph to this diagnostic. */
+ gcc_assert (m_enode);
+}
+
+/* saved_diagnostic's dtor. */
+
+saved_diagnostic::~saved_diagnostic ()
+{
+ delete m_stmt_finder;
+ delete m_d;
+}
+
+/* class diagnostic_manager. */
+
+/* diagnostic_manager's ctor. */
+
+diagnostic_manager::diagnostic_manager (logger *logger, int verbosity)
+: log_user (logger), m_verbosity (verbosity)
+{
+}
+
+/* Queue pending_diagnostic D at ENODE for later emission. */
+
+void
+diagnostic_manager::add_diagnostic (const state_machine *sm,
+ const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *finder,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d)
+{
+ LOG_FUNC (get_logger ());
+
+ /* We must have an enode in order to be able to look for paths
+ through the exploded_graph to the diagnostic. */
+ gcc_assert (enode);
+
+ saved_diagnostic *sd
+ = new saved_diagnostic (sm, enode, snode, stmt, finder, var, state, d);
+ m_saved_diagnostics.safe_push (sd);
+ if (get_logger ())
+ log ("adding saved diagnostic %i at SN %i: %qs",
+ m_saved_diagnostics.length () - 1,
+ snode->m_index, d->get_kind ());
+}
+
+/* Queue pending_diagnostic D at ENODE for later emission. */
+
+void
+diagnostic_manager::add_diagnostic (const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *finder,
+ pending_diagnostic *d)
+{
+ gcc_assert (enode);
+ add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, 0, d);
+}
+
+/* A class for identifying sets of duplicated pending_diagnostic.
+
+ We want to find the simplest dedupe_candidate amongst those that share a
+ dedupe_key. */
+
+class dedupe_key
+{
+public:
+ dedupe_key (const saved_diagnostic &sd,
+ const exploded_path &epath)
+ : m_sd (sd), m_stmt (sd.m_stmt)
+ {
+ /* Support deferring the choice of stmt until after an emission path has
+ been built, using an optional stmt_finder. */
+ if (m_stmt == NULL)
+ {
+ gcc_assert (sd.m_stmt_finder);
+ m_stmt = sd.m_stmt_finder->find_stmt (epath);
+ }
+ gcc_assert (m_stmt);
+ }
+
+ hashval_t hash () const
+ {
+ inchash::hash hstate;
+ hstate.add_ptr (m_stmt);
+ // TODO: m_sd
+ return hstate.end ();
+ }
+ bool operator== (const dedupe_key &other) const
+ {
+ return (m_sd == other.m_sd
+ && m_stmt == other.m_stmt);
+ }
+
+ location_t get_location () const
+ {
+ return m_stmt->location;
+ }
+
+ /* A qsort comparator for use by dedupe_winners::emit_best
+ to sort them into location_t order. */
+
+ static int
+ comparator (const void *p1, const void *p2)
+ {
+ const dedupe_key *pk1 = *(const dedupe_key * const *)p1;
+ const dedupe_key *pk2 = *(const dedupe_key * const *)p2;
+
+ location_t loc1 = pk1->get_location ();
+ location_t loc2 = pk2->get_location ();
+
+ return linemap_compare_locations (line_table, loc2, loc1);
+ }
+
+ const saved_diagnostic &m_sd;
+ const gimple *m_stmt;
+};
+
+/* The value of a slot for a dedupe_key within dedupe_winners:
+ the exploded_path for the best candidate for that key, and the
+ number of duplicates seen so far. */
+
+class dedupe_candidate
+{
+public:
+ // has the exploded_path
+ dedupe_candidate (const shortest_exploded_paths &sp,
+ const saved_diagnostic &sd)
+ : m_epath (sp.get_shortest_path (sd.m_enode)),
+ m_num_dupes (0)
+ {
+ }
+
+ unsigned length () const { return m_epath.length (); }
+ const exploded_path &get_path () const { return m_epath; }
+
+ void add_duplicate () { m_num_dupes++; }
+ int get_num_dupes () const { return m_num_dupes; }
+
+private:
+ exploded_path m_epath;
+public:
+ int m_num_dupes;
+};
+
+/* Traits for use by dedupe_winners. */
+
+class dedupe_hash_map_traits
+{
+public:
+ typedef const dedupe_key *key_type;
+ typedef dedupe_candidate *value_type;
+ typedef dedupe_candidate *compare_type;
+
+ static inline hashval_t hash (const key_type &v)
+ {
+ return v->hash ();
+ }
+ static inline bool equal_keys (const key_type &k1, const key_type &k2)
+ {
+ return *k1 == *k2;
+ }
+ template <typename T>
+ static inline void remove (T &)
+ {
+ // TODO
+ }
+ template <typename T>
+ static inline void mark_deleted (T &entry)
+ {
+ entry.m_key = reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline void mark_empty (T &entry)
+ {
+ entry.m_key = NULL;
+ }
+ template <typename T>
+ static inline bool is_deleted (const T &entry)
+ {
+ return entry.m_key == reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline bool is_empty (const T &entry)
+ {
+ return entry.m_key == NULL;
+ }
+ static const bool empty_zero_p = true;
+};
+
+/* A class for deduplicating diagnostics and finding (and emitting) the
+ best diagnostic within each partition. */
+
+class dedupe_winners
+{
+public:
+ ~dedupe_winners ()
+ {
+ /* Delete all keys and candidates. */
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ delete (*iter).first;
+ delete (*iter).second;
+ }
+ }
+
+ /* Determine an exploded_path for SD using SP and, if it's feasible,
+ determine if it's the best seen so far for its dedupe_key.
+ Retain the winner for each dedupe_key, and discard the rest. */
+
+ void add (logger *logger,
+ const shortest_exploded_paths &sp,
+ const saved_diagnostic &sd)
+ {
+ /* Build a dedupe_candidate for SD.
+ This uses SP to build an exploded_path. */
+ dedupe_candidate *dc = new dedupe_candidate (sp, sd);
+
+ /* Verify that the epath is feasible.
+ State-merging means that not every path in the epath corresponds
+ to a feasible one w.r.t. states.
+ Here we simply check each duplicate saved_diagnostic's
+ shortest_path, and reject any that aren't feasible.
+ This could introduce false negatives, as there could be longer
+ feasible paths within the egraph. */
+ if (logger)
+ logger->log ("considering %qs at SN: %i",
+ sd.m_d->get_kind (), sd.m_snode->m_index);
+ if (!dc->get_path ().feasible_p (logger))
+ {
+ if (logger)
+ logger->log ("rejecting %qs at SN: %i"
+ " due to infeasible path",
+ sd.m_d->get_kind (), sd.m_snode->m_index);
+ delete dc;
+ return;
+ }
+ else
+ if (logger)
+ logger->log ("accepting %qs at SN: %i with feasible path",
+ sd.m_d->get_kind (), sd.m_snode->m_index);
+
+ dedupe_key *key = new dedupe_key (sd, dc->get_path ());
+ if (dedupe_candidate **slot = m_map.get (key))
+ {
+ (*slot)->add_duplicate ();
+
+ if (dc->length () < (*slot)->length ())
+ {
+ /* We've got a shorter path for the key; replace
+ the current candidate. */
+ dc->m_num_dupes = (*slot)->get_num_dupes ();
+ delete *slot;
+ *slot = dc;
+ }
+ else
+ /* We haven't beaten the current best candidate;
+ drop the new candidate. */
+ delete dc;
+ delete key;
+ }
+ else
+ /* This is the first candidate for this key. */
+ m_map.put (key, dc);
+ }
+
+ /* Emit the simplest diagnostic within each set. */
+
+ void emit_best (diagnostic_manager *dm,
+ const exploded_graph &eg)
+ {
+ LOG_SCOPE (dm->get_logger ());
+
+ /* Get keys into a vec for sorting. */
+ auto_vec<const dedupe_key *> keys (m_map.elements ());
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ keys.quick_push ((*iter).first);
+
+ dm->log ("# keys after de-duplication: %i", keys.length ());
+
+ /* Sort into a good emission order. */
+ keys.qsort (dedupe_key::comparator);
+
+ /* Emit the best candidate for each key. */
+ int i;
+ const dedupe_key *key;
+ FOR_EACH_VEC_ELT (keys, i, key)
+ {
+ dedupe_candidate **slot = m_map.get (key);
+ gcc_assert (*slot);
+ const dedupe_candidate &dc = **slot;
+
+ dm->emit_saved_diagnostic (eg, key->m_sd,
+ dc.get_path (), key->m_stmt,
+ dc.get_num_dupes ());
+ }
+ }
+
+private:
+
+ /* This maps from each dedupe_key to a current best dedupe_candidate. */
+
+ typedef hash_map<const dedupe_key *, dedupe_candidate *,
+ dedupe_hash_map_traits> map_t;
+ map_t m_map;
+};
+
+/* Emit all saved diagnostics. */
+
+void
+diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
+{
+ LOG_SCOPE (get_logger ());
+ auto_timevar tv (TV_ANALYZER_DIAGNOSTICS);
+ log ("# saved diagnostics: %i", m_saved_diagnostics.length ());
+
+ if (m_saved_diagnostics.length () == 0)
+ return;
+
+ /* Compute the shortest_paths once, sharing it between all diagnostics. */
+ shortest_exploded_paths sp (eg, eg.get_origin ());
+
+ /* Iterate through all saved diagnostics, adding them to a dedupe_winners
+ instance. This partitions the saved diagnostics by dedupe_key,
+ generating exploded_paths for them, and retaining the best one in each
+ partition. */
+ dedupe_winners best_candidates;
+
+ int i;
+ saved_diagnostic *sd;
+ FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
+ best_candidates.add (get_logger (), sp, *sd);
+
+ /* For each dedupe-key, call emit_saved_diagnostic on the "best"
+ saved_diagnostic. */
+ best_candidates.emit_best (this, eg);
+}
+
+/* Given a saved_diagnostic SD at STMT with feasible path EPATH through EG,
+ create an checker_path of suitable events and use it to call
+ SD's underlying pending_diagnostic "emit" vfunc to emit a diagnostic. */
+
+void
+diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
+ const saved_diagnostic &sd,
+ const exploded_path &epath,
+ const gimple *stmt,
+ int num_dupes)
+{
+ LOG_SCOPE (get_logger ());
+ log ("sd: %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index);
+ log ("num dupes: %i", num_dupes);
+
+ pretty_printer *pp = global_dc->printer->clone ();
+
+ checker_path emission_path;
+
+ /* Populate emission_path with a full description of EPATH. */
+ build_emission_path (eg, epath, &emission_path);
+
+ /* Now prune it to just cover the most pertinent events. */
+ prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state);
+
+ /* Add a final event to the path, covering the diagnostic itself.
+ We use the final enode from the epath, which might be different from
+ the sd.m_enode, as the dedupe code doesn't care about enodes, just
+ snodes. */
+ emission_path.add_final_event (sd.m_sm, epath.get_final_enode (), stmt,
+ sd.m_var, sd.m_state);
+
+ /* The "final" event might not be final; if the saved_diagnostic has a
+ trailing eedge stashed, add any events for it. This is for use
+ in handling longjmp, to show where a longjmp is rewinding to. */
+ if (sd.m_trailing_eedge)
+ add_events_for_eedge (*sd.m_trailing_eedge, eg.get_ext_state (),
+ &emission_path);
+
+ emission_path.prepare_for_emission (sd.m_d);
+
+ gcc_rich_location rich_loc (stmt->location);
+ rich_loc.set_path (&emission_path);
+
+ auto_diagnostic_group d;
+ auto_cfun sentinel (sd.m_snode->m_fun);
+ if (sd.m_d->emit (&rich_loc))
+ {
+ if (num_dupes > 0)
+ inform_n (stmt->location, num_dupes,
+ "%i duplicate", "%i duplicates",
+ num_dupes);
+ }
+ delete pp;
+}
+
+/* Given a state change to DST_REP, determine a tree that gives the origin
+ of that state at STMT, using DST_STATE's region model, so that state
+ changes based on assignments can be tracked back to their origins.
+
+ For example, if we have
+
+ (S1) _1 = malloc (64);
+ (S2) EXPR = _1;
+
+ then at stmt S2 we can get the origin of EXPR's state as being _1,
+ and thus track the allocation back to S1. */
+
+static tree
+get_any_origin (const gimple *stmt,
+ tree dst_rep,
+ const program_state &dst_state)
+{
+ if (!stmt)
+ return NULL_TREE;
+
+ gcc_assert (dst_rep);
+
+ if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+ {
+ tree lhs = gimple_assign_lhs (assign);
+ /* Use region IDs to compare lhs with DST_REP. */
+ if (dst_state.m_region_model->get_lvalue (lhs, NULL)
+ == dst_state.m_region_model->get_lvalue (dst_rep, NULL))
+ {
+ tree rhs1 = gimple_assign_rhs1 (assign);
+ enum tree_code op = gimple_assign_rhs_code (assign);
+ switch (op)
+ {
+ default:
+ //gcc_unreachable (); // TODO
+ break;
+ case COMPONENT_REF:
+ case SSA_NAME:
+ return rhs1;
+ }
+ }
+ }
+ return NULL_TREE;
+}
+
+/* Emit a "path" of events to EMISSION_PATH describing the exploded path
+ EPATH within EG. */
+
+void
+diagnostic_manager::build_emission_path (const exploded_graph &eg,
+ const exploded_path &epath,
+ checker_path *emission_path) const
+{
+ LOG_SCOPE (get_logger ());
+ const extrinsic_state &ext_state = eg.get_ext_state ();
+ for (unsigned i = 0; i < epath.m_edges.length (); i++)
+ {
+ const exploded_edge *eedge = epath.m_edges[i];
+ add_events_for_eedge (*eedge, ext_state, emission_path);
+ }
+}
+
+/* Subclass of state_change_visitor that creates state_change_event
+ instances. */
+
+class state_change_event_creator : public state_change_visitor
+{
+public:
+ state_change_event_creator (const exploded_edge &eedge,
+ checker_path *emission_path)
+ : m_eedge (eedge),
+ m_emission_path (emission_path)
+ {}
+
+ bool on_global_state_change (const state_machine &sm,
+ state_machine::state_t src_sm_val,
+ state_machine::state_t dst_sm_val)
+ FINAL OVERRIDE
+ {
+ const exploded_node *src_node = m_eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ const exploded_node *dst_node = m_eedge.m_dest;
+ const gimple *stmt = src_point.get_stmt ();
+ const supernode *supernode = src_point.get_supernode ();
+ const program_state &dst_state = dst_node->get_state ();
+
+ int stack_depth = src_stack_depth;
+
+ m_emission_path->add_event (new state_change_event (supernode,
+ stmt,
+ stack_depth,
+ sm,
+ NULL_TREE,
+ src_sm_val,
+ dst_sm_val,
+ NULL_TREE,
+ dst_state));
+ return false;
+ }
+
+ bool on_state_change (const state_machine &sm,
+ state_machine::state_t src_sm_val,
+ state_machine::state_t dst_sm_val,
+ tree dst_rep,
+ svalue_id dst_origin_sid) FINAL OVERRIDE
+ {
+ const exploded_node *src_node = m_eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ const exploded_node *dst_node = m_eedge.m_dest;
+ const gimple *stmt = src_point.get_stmt ();
+ const supernode *supernode = src_point.get_supernode ();
+ const program_state &dst_state = dst_node->get_state ();
+
+ int stack_depth = src_stack_depth;
+
+ if (m_eedge.m_sedge
+ && m_eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE)
+ {
+ supernode = src_point.get_supernode ();
+ stmt = supernode->get_last_stmt ();
+ stack_depth = src_stack_depth;
+ }
+
+ /* Bulletproofing for state changes at calls/returns;
+ TODO: is there a better way? */
+ if (!stmt)
+ return false;
+
+ tree origin_rep
+ = dst_state.get_representative_tree (dst_origin_sid);
+
+ if (origin_rep == NULL_TREE)
+ origin_rep = get_any_origin (stmt, dst_rep, dst_state);
+ m_emission_path->add_event (new state_change_event (supernode,
+ stmt,
+ stack_depth,
+ sm,
+ dst_rep,
+ src_sm_val,
+ dst_sm_val,
+ origin_rep,
+ dst_state));
+ return false;
+ }
+
+ const exploded_edge &m_eedge;
+ checker_path *m_emission_path;
+};
+
+/* Compare SRC_STATE and DST_STATE (which use EXT_STATE), and call
+ VISITOR's on_state_change for every sm-state change that occurs
+ to a tree, and on_global_state_change for every global state change
+ that occurs.
+
+ This determines the state changes that ought to be reported to
+ the user: a combination of the effects of changes to sm_state_map
+ (which maps svalues to sm-states), and of region_model changes
+ (which map trees to svalues).
+
+ Bail out early and return true if any call to on_global_state_change
+ or on_state_change returns true, otherwise return false.
+
+ This is split out to make it easier to experiment with changes to
+ exploded_node granularity (so that we can observe what state changes
+ lead to state_change_events being emitted). */
+
+bool
+for_each_state_change (const program_state &src_state,
+ const program_state &dst_state,
+ const extrinsic_state &ext_state,
+ state_change_visitor *visitor)
+{
+ gcc_assert (src_state.m_checker_states.length ()
+ == ext_state.m_checkers.length ());
+ gcc_assert (dst_state.m_checker_states.length ()
+ == ext_state.m_checkers.length ());
+ for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
+ {
+ const state_machine &sm = ext_state.get_sm (i);
+ const sm_state_map &src_smap = *src_state.m_checker_states[i];
+ const sm_state_map &dst_smap = *dst_state.m_checker_states[i];
+
+ /* Add events for any global state changes. */
+ if (src_smap.get_global_state () != dst_smap.get_global_state ())
+ if (visitor->on_global_state_change (sm,
+ src_smap.get_global_state (),
+ dst_smap.get_global_state ()))
+ return true;
+
+ /* Add events for per-svalue state changes. */
+ for (sm_state_map::iterator_t iter = dst_smap.begin ();
+ iter != dst_smap.end ();
+ ++iter)
+ {
+ /* Ideally we'd directly compare the SM state between src state
+ and dst state, but there's no guarantee that the IDs can
+ be meaningfully compared. */
+ svalue_id dst_sid = (*iter).first;
+ state_machine::state_t dst_sm_val = (*iter).second.m_state;
+
+ auto_vec<path_var> dst_pvs;
+ dst_state.m_region_model->get_path_vars_for_svalue (dst_sid,
+ &dst_pvs);
+
+ unsigned j;
+ path_var *dst_pv;
+ FOR_EACH_VEC_ELT (dst_pvs, j, dst_pv)
+ {
+ tree dst_rep = dst_pv->m_tree;
+ gcc_assert (dst_rep);
+ if (dst_pv->m_stack_depth
+ >= src_state.m_region_model->get_stack_depth ())
+ continue;
+ svalue_id src_sid
+ = src_state.m_region_model->get_rvalue (*dst_pv, NULL);
+ if (src_sid.null_p ())
+ continue;
+ state_machine::state_t src_sm_val = src_smap.get_state (src_sid);
+ if (dst_sm_val != src_sm_val)
+ {
+ svalue_id dst_origin_sid = (*iter).second.m_origin;
+ if (visitor->on_state_change (sm, src_sm_val, dst_sm_val,
+ dst_rep, dst_origin_sid))
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/* Subroutine of diagnostic_manager::build_emission_path.
+ Add any events for EEDGE to EMISSION_PATH. */
+
+void
+diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge,
+ const extrinsic_state &ext_state,
+ checker_path *emission_path) const
+{
+ const exploded_node *src_node = eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const exploded_node *dst_node = eedge.m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+ if (get_logger ())
+ {
+ get_logger ()->start_log_line ();
+ pretty_printer *pp = get_logger ()->get_printer ();
+ pp_printf (pp, "EN %i -> EN %i: ",
+ eedge.m_src->m_index,
+ eedge.m_dest->m_index);
+ src_point.print (pp, format (false));
+ pp_string (pp, "-> ");
+ dst_point.print (pp, format (false));
+ get_logger ()->end_log_line ();
+ }
+ const program_state &src_state = src_node->get_state ();
+ const program_state &dst_state = dst_node->get_state ();
+
+ /* Add state change events for the states that have changed.
+ We add these before events for superedges, so that if we have a
+ state_change_event due to following an edge, we'll get this sequence
+ of events:
+
+ | if (!ptr)
+ | ~
+ | |
+ | (1) assuming 'ptr' is non-NULL (state_change_event)
+ | (2) following 'false' branch... (start_cfg_edge_event)
+ ...
+ | do_something (ptr);
+ | ~~~~~~~~~~~~~^~~~~
+ | |
+ | (3) ...to here (end_cfg_edge_event). */
+ state_change_event_creator visitor (eedge, emission_path);
+ for_each_state_change (src_state, dst_state, ext_state,
+ &visitor);
+
+ /* Allow non-standard edges to add events, e.g. when rewinding from
+ longjmp to a setjmp. */
+ if (eedge.m_custom_info)
+ eedge.m_custom_info->add_events_to_path (emission_path, eedge);
+
+ /* Add events for superedges, function entries, and for statements. */
+ switch (dst_point.get_kind ())
+ {
+ default:
+ break;
+ case PK_BEFORE_SUPERNODE:
+ if (src_point.get_kind () == PK_AFTER_SUPERNODE)
+ {
+ if (eedge.m_sedge)
+ add_events_for_superedge (eedge, emission_path);
+ }
+ /* Add function entry events. */
+ if (dst_point.get_supernode ()->entry_p ())
+ {
+ emission_path->add_event
+ (new function_entry_event
+ (dst_point.get_supernode ()->get_start_location (),
+ dst_point.get_fndecl (),
+ dst_stack_depth));
+ }
+ break;
+ case PK_BEFORE_STMT:
+ {
+ const gimple *stmt = dst_point.get_stmt ();
+ if (is_setjmp_call_p (stmt))
+ emission_path->add_event
+ (new setjmp_event (stmt->location,
+ dst_node,
+ dst_point.get_fndecl (),
+ dst_stack_depth));
+ else
+ emission_path->add_event
+ (new statement_event (stmt,
+ dst_point.get_fndecl (),
+ dst_stack_depth, dst_state));
+ }
+ break;
+ }
+}
+
+/* Subroutine of diagnostic_manager::add_events_for_eedge
+ where EEDGE has an underlying superedge i.e. a CFG edge,
+ or an interprocedural call/return.
+ Add any events for the superedge to EMISSION_PATH. */
+
+void
+diagnostic_manager::add_events_for_superedge (const exploded_edge &eedge,
+ checker_path *emission_path)
+ const
+{
+ gcc_assert (eedge.m_sedge);
+
+ const exploded_node *src_node = eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const exploded_node *dst_node = eedge.m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+ const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+
+ switch (eedge.m_sedge->m_kind)
+ {
+ case SUPEREDGE_CFG_EDGE:
+ {
+ emission_path->add_event
+ (new start_cfg_edge_event (eedge,
+ (last_stmt
+ ? last_stmt->location
+ : UNKNOWN_LOCATION),
+ src_point.get_fndecl (),
+ src_stack_depth));
+ emission_path->add_event
+ (new end_cfg_edge_event (eedge,
+ dst_point.get_supernode ()->get_start_location (),
+ dst_point.get_fndecl (),
+ dst_stack_depth));
+ }
+ break;
+
+ case SUPEREDGE_CALL:
+ {
+ emission_path->add_event
+ (new call_event (eedge,
+ (last_stmt
+ ? last_stmt->location
+ : UNKNOWN_LOCATION),
+ src_point.get_fndecl (),
+ src_stack_depth));
+ }
+ break;
+
+ case SUPEREDGE_INTRAPROCEDURAL_CALL:
+ {
+ /* TODO: add a subclass for this, or generate events for the
+ summary. */
+ emission_path->add_event
+ (new debug_event ((last_stmt
+ ? last_stmt->location
+ : UNKNOWN_LOCATION),
+ src_point.get_fndecl (),
+ src_stack_depth,
+ "call summary"));
+ }
+ break;
+
+ case SUPEREDGE_RETURN:
+ {
+ const return_superedge *return_edge
+ = as_a <const return_superedge *> (eedge.m_sedge);
+
+ const gcall *call_stmt = return_edge->get_call_stmt ();
+ emission_path->add_event
+ (new return_event (eedge,
+ (call_stmt
+ ? call_stmt->location
+ : UNKNOWN_LOCATION),
+ dst_point.get_fndecl (),
+ dst_stack_depth));
+ }
+ break;
+ }
+}
+
+/* Prune PATH, based on the verbosity level, to the most pertinent
+ events for a diagnostic that involves VAR ending in state STATE
+ (for state machine SM).
+
+ PATH is updated in place, and the redundant checker_events are deleted.
+
+ As well as deleting events, call record_critical_state on events in
+ which state critical to the pending_diagnostic is being handled; see
+ the comment for diagnostic_manager::prune_for_sm_diagnostic. */
+
+void
+diagnostic_manager::prune_path (checker_path *path,
+ const state_machine *sm,
+ tree var,
+ state_machine::state_t state) const
+{
+ LOG_FUNC (get_logger ());
+ path->maybe_log (get_logger (), "path");
+ prune_for_sm_diagnostic (path, sm, var, state);
+ prune_interproc_events (path);
+ finish_pruning (path);
+ path->maybe_log (get_logger (), "pruned");
+}
+
+/* First pass of diagnostic_manager::prune_path: apply verbosity level,
+ pruning unrelated state change events.
+
+ Iterate backwards through PATH, skipping state change events that aren't
+ VAR but update the pertinent VAR when state-copying occurs.
+
+ As well as deleting events, call record_critical_state on events in
+ which state critical to the pending_diagnostic is being handled, so
+ that the event's get_desc vfunc can potentially supply a more precise
+ description of the event to the user.
+ e.g. improving
+ "calling 'foo' from 'bar'"
+ to
+ "passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as param 1"
+ when the diagnostic relates to later dereferencing 'ptr'. */
+
+void
+diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
+ const state_machine *sm,
+ tree var,
+ state_machine::state_t state) const
+{
+ int idx = path->m_events.length () - 1;
+ while (idx >= 0 && idx < (signed)path->m_events.length ())
+ {
+ checker_event *base_event = path->m_events[idx];
+ if (get_logger ())
+ {
+ if (sm)
+ {
+ if (var)
+ log ("considering event %i, with var: %qE, state: %qs",
+ idx, var, sm->get_state_name (state));
+ else
+ log ("considering event %i, with global state: %qs",
+ idx, sm->get_state_name (state));
+ }
+ else
+ log ("considering event %i", idx);
+ }
+ switch (base_event->m_kind)
+ {
+ default:
+ gcc_unreachable ();
+
+ case EK_DEBUG:
+ if (m_verbosity < 3)
+ {
+ log ("filtering event %i: debug event", idx);
+ path->delete_event (idx);
+ }
+ break;
+
+ case EK_CUSTOM:
+ /* Don't filter custom events. */
+ break;
+
+ case EK_STMT:
+ {
+ /* If this stmt is the origin of "var", update var. */
+ if (var)
+ {
+ statement_event *stmt_event = (statement_event *)base_event;
+ tree new_var = get_any_origin (stmt_event->m_stmt, var,
+ stmt_event->m_dst_state);
+ if (new_var)
+ {
+ log ("event %i: switching var of interest from %qE to %qE",
+ idx, var, new_var);
+ var = new_var;
+ }
+ }
+ if (m_verbosity < 3)
+ {
+ log ("filtering event %i: statement event", idx);
+ path->delete_event (idx);
+ }
+ }
+ break;
+
+ case EK_FUNCTION_ENTRY:
+ if (m_verbosity < 1)
+ {
+ log ("filtering event %i: function entry", idx);
+ path->delete_event (idx);
+ }
+ break;
+
+ case EK_STATE_CHANGE:
+ {
+ state_change_event *state_change = (state_change_event *)base_event;
+ if (state_change->get_lvalue (state_change->m_var)
+ == state_change->get_lvalue (var))
+ {
+ if (state_change->m_origin)
+ {
+ log ("event %i: switching var of interest from %qE to %qE",
+ idx, var, state_change->m_origin);
+ var = state_change->m_origin;
+ }
+ log ("event %i: switching state of interest from %qs to %qs",
+ idx, sm->get_state_name (state_change->m_to),
+ sm->get_state_name (state_change->m_from));
+ state = state_change->m_from;
+ }
+ else if (m_verbosity < 3)
+ {
+ if (var)
+ log ("filtering event %i:"
+ " state change to %qE unrelated to %qE",
+ idx, state_change->m_var, var);
+ else
+ log ("filtering event %i: state change to %qE",
+ idx, state_change->m_var);
+ path->delete_event (idx);
+ }
+ }
+ break;
+
+ case EK_START_CFG_EDGE:
+ {
+ cfg_edge_event *event = (cfg_edge_event *)base_event;
+ const cfg_superedge& cfg_superedge
+ = event->get_cfg_superedge ();
+ const supernode *dest = event->m_sedge->m_dest;
+ /* Do we have an SSA_NAME defined via a phi node in
+ the dest CFG node? */
+ if (var && TREE_CODE (var) == SSA_NAME)
+ if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
+ {
+ if (gphi *phi
+ = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
+ {
+ /* Update var based on its phi node. */
+ tree old_var = var;
+ var = cfg_superedge.get_phi_arg (phi);
+ log ("updating from %qE to %qE based on phi node",
+ old_var, var);
+ if (get_logger ())
+ {
+ pretty_printer pp;
+ pp_gimple_stmt_1 (&pp, phi, 0, (dump_flags_t)0);
+ log (" phi: %s", pp_formatted_text (&pp));
+ }
+ }
+ }
+
+ /* TODO: is this edge significant to var?
+ See if var can be in other states in the dest, but not
+ in other states in the src?
+ Must have multiple sibling edges. */
+
+ if (event->should_filter_p (m_verbosity))
+ {
+ log ("filtering event %i: CFG edge", idx);
+ path->delete_event (idx);
+ /* Also delete the corresponding EK_END_CFG_EDGE. */
+ gcc_assert (path->m_events[idx]->m_kind == EK_END_CFG_EDGE);
+ path->delete_event (idx);
+ }
+ }
+ break;
+
+ case EK_END_CFG_EDGE:
+ /* These come in pairs with EK_START_CFG_EDGE events and are
+ filtered when their start event is filtered. */
+ break;
+
+ case EK_CALL_EDGE:
+ {
+ call_event *event = (call_event *)base_event;
+ const callgraph_superedge& cg_superedge
+ = event->get_callgraph_superedge ();
+ callsite_expr expr;
+ tree caller_var
+ = cg_superedge.map_expr_from_callee_to_caller (var, &expr);
+ if (caller_var)
+ {
+ log ("event %i:"
+ " switching var of interest"
+ " from %qE in callee to %qE in caller",
+ idx, var, caller_var);
+ var = caller_var;
+ if (expr.param_p ())
+ event->record_critical_state (var, state);
+ }
+ }
+ break;
+
+ case EK_RETURN_EDGE:
+ // TODO: potentially update var/state based on return value,
+ // args etc
+ {
+ if (var)
+ {
+ return_event *event = (return_event *)base_event;
+ const callgraph_superedge& cg_superedge
+ = event->get_callgraph_superedge ();
+ callsite_expr expr;
+ tree callee_var
+ = cg_superedge.map_expr_from_caller_to_callee (var, &expr);
+ if (callee_var)
+ {
+ log ("event %i:"
+ " switching var of interest"
+ " from %qE in caller to %qE in callee",
+ idx, var, callee_var);
+ var = callee_var;
+ if (expr.return_value_p ())
+ event->record_critical_state (var, state);
+ }
+ }
+ }
+ break;
+
+ case EK_SETJMP:
+ /* TODO: only show setjmp_events that matter i.e. those for which
+ there is a later rewind event using them. */
+ case EK_REWIND_FROM_LONGJMP:
+ case EK_REWIND_TO_SETJMP:
+ break;
+
+ case EK_WARNING:
+ /* Always show the final "warning" event in the path. */
+ break;
+ }
+ idx--;
+ }
+}
+
+/* Second pass of diagnostic_manager::prune_path: remove redundant
+ interprocedural information.
+
+ For example, given:
+ (1)- calling "f2" from "f1"
+ (2)--- entry to "f2"
+ (3)--- calling "f3" from "f2"
+ (4)----- entry to "f3"
+ (5)--- returning to "f2" to "f3"
+ (6)- returning to "f1" to "f2"
+ with no other intervening events, then none of these events are
+ likely to be interesting to the user.
+
+ Prune [..., call, function-entry, return, ...] triples repeatedly
+ until nothing has changed. For the example above, this would
+ remove events (3, 4, 5), and then remove events (1, 2, 6). */
+
+void
+diagnostic_manager::prune_interproc_events (checker_path *path) const
+{
+ bool changed = false;
+ do
+ {
+ changed = false;
+ int idx = path->m_events.length () - 1;
+ while (idx >= 0)
+ {
+ /* Prune [..., call, function-entry, return, ...] triples. */
+ if (idx + 2 < (signed)path->m_events.length ()
+ && path->m_events[idx]->is_call_p ()
+ && path->m_events[idx + 1]->is_function_entry_p ()
+ && path->m_events[idx + 2]->is_return_p ())
+ {
+ if (get_logger ())
+ {
+ label_text desc (path->m_events[idx]->get_desc (false));
+ log ("filtering events %i-%i:"
+ " irrelevant call/entry/return: %s",
+ idx, idx + 2, desc.m_buffer);
+ desc.maybe_free ();
+ }
+ path->delete_event (idx + 2);
+ path->delete_event (idx + 1);
+ path->delete_event (idx);
+ changed = true;
+ idx--;
+ continue;
+ }
+
+ /* Prune [..., call, return, ...] pairs
+ (for -fanalyzer-verbosity=0). */
+ if (idx + 1 < (signed)path->m_events.length ()
+ && path->m_events[idx]->is_call_p ()
+ && path->m_events[idx + 1]->is_return_p ())
+ {
+ if (get_logger ())
+ {
+ label_text desc (path->m_events[idx]->get_desc (false));
+ log ("filtering events %i-%i:"
+ " irrelevant call/return: %s",
+ idx, idx + 1, desc.m_buffer);
+ desc.maybe_free ();
+ }
+ path->delete_event (idx + 1);
+ path->delete_event (idx);
+ changed = true;
+ idx--;
+ continue;
+ }
+
+ idx--;
+ }
+
+ }
+ while (changed);
+}
+
+/* Final pass of diagnostic_manager::prune_path.
+
+ If all we're left with is in one function, then filter function entry
+ events. */
+
+void
+diagnostic_manager::finish_pruning (checker_path *path) const
+{
+ if (!path->interprocedural_p ())
+ {
+ int idx = path->m_events.length () - 1;
+ while (idx >= 0 && idx < (signed)path->m_events.length ())
+ {
+ checker_event *base_event = path->m_events[idx];
+ if (base_event->m_kind == EK_FUNCTION_ENTRY)
+ {
+ log ("filtering event %i:"
+ " function entry for purely intraprocedural path", idx);
+ path->delete_event (idx);
+ }
+ idx--;
+ }
+ }
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h
new file mode 100644
index 00000000000..aa939430348
--- /dev/null
+++ b/gcc/analyzer/diagnostic-manager.h
@@ -0,0 +1,135 @@
+/* Classes for saving, deduplicating, and emitting analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
+#define GCC_ANALYZER_DIAGNOSTIC_MANAGER_H
+
+/* A to-be-emitted diagnostic stored within diagnostic_manager. */
+
+class saved_diagnostic
+{
+public:
+ saved_diagnostic (const state_machine *sm,
+ const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *stmt_finder,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d);
+ ~saved_diagnostic ();
+
+ bool operator== (const saved_diagnostic &other) const
+ {
+ return (m_sm == other.m_sm
+ /* We don't compare m_enode. */
+ && m_snode == other.m_snode
+ && m_stmt == other.m_stmt
+ /* We don't compare m_stmt_finder. */
+ && m_var == other.m_var
+ && m_state == other.m_state
+ && m_d->equal_p (*other.m_d)
+ && m_trailing_eedge == other.m_trailing_eedge);
+ }
+
+ //private:
+ const state_machine *m_sm;
+ const exploded_node *m_enode;
+ const supernode *m_snode;
+ const gimple *m_stmt;
+ stmt_finder *m_stmt_finder;
+ tree m_var;
+ state_machine::state_t m_state;
+ pending_diagnostic *m_d;
+ exploded_edge *m_trailing_eedge;
+
+private:
+ DISABLE_COPY_AND_ASSIGN (saved_diagnostic);
+};
+
+/* A class with responsibility for saving pending diagnostics, so that
+ they can be emitted after the exploded_graph is complete.
+ This lets us de-duplicate diagnostics, and find the shortest path
+ for each similar diagnostic, potentially using edges that might
+ not have been found when each diagnostic was first saved.
+
+ This also lets us compute shortest_paths once, rather than
+ per-diagnostic. */
+
+class diagnostic_manager : public log_user
+{
+public:
+ diagnostic_manager (logger *logger, int verbosity);
+
+ void add_diagnostic (const state_machine *sm,
+ const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *finder,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d);
+
+ void add_diagnostic (const exploded_node *enode,
+ const supernode *snode, const gimple *stmt,
+ stmt_finder *finder,
+ pending_diagnostic *d);
+
+ void emit_saved_diagnostics (const exploded_graph &eg);
+
+ void emit_saved_diagnostic (const exploded_graph &eg,
+ const saved_diagnostic &sd,
+ const exploded_path &epath,
+ const gimple *stmt,
+ int num_dupes);
+
+ unsigned get_num_diagnostics () const
+ {
+ return m_saved_diagnostics.length ();
+ }
+ saved_diagnostic *get_saved_diagnostic (unsigned idx)
+ {
+ return m_saved_diagnostics[idx];
+ }
+
+private:
+ void build_emission_path (const exploded_graph &eg,
+ const exploded_path &epath,
+ checker_path *emission_path) const;
+
+ void add_events_for_eedge (const exploded_edge &eedge,
+ const extrinsic_state &ext_state,
+ checker_path *emission_path) const;
+
+ void add_events_for_superedge (const exploded_edge &eedge,
+ checker_path *emission_path) const;
+
+ void prune_path (checker_path *path,
+ const state_machine *sm,
+ tree var, state_machine::state_t state) const;
+
+ void prune_for_sm_diagnostic (checker_path *path,
+ const state_machine *sm,
+ tree var,
+ state_machine::state_t state) const;
+ void prune_interproc_events (checker_path *path) const;
+ void finish_pruning (checker_path *path) const;
+
+ auto_delete_vec<saved_diagnostic> m_saved_diagnostics;
+ const int m_verbosity;
+};
+
+#endif /* GCC_ANALYZER_DIAGNOSTIC_MANAGER_H */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
new file mode 100644
index 00000000000..720fa219d16
--- /dev/null
+++ b/gcc/analyzer/engine.cc
@@ -0,0 +1,3614 @@
+/* The analysis "engine".
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "fold-const.h"
+#include "gcc-rich-location.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "diagnostic-core.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-path.h"
+#include "function.h"
+#include "pretty-print.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/analysis-plan.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/state-purge.h"
+
+/* For an overview, see gcc/doc/analyzer.texi. */
+
+#if ENABLE_ANALYZER
+
+static int readability_comparator (const void *p1, const void *p2);
+
+/* class impl_region_model_context : public region_model_context, public log_user. */
+
+impl_region_model_context::
+impl_region_model_context (exploded_graph &eg,
+ const exploded_node *enode_for_diag,
+ const program_state *old_state,
+ program_state *new_state,
+ state_change *change,
+ const gimple *stmt,
+ stmt_finder *stmt_finder)
+: m_eg (&eg), m_logger (eg.get_logger ()),
+ m_enode_for_diag (enode_for_diag),
+ m_old_state (old_state),
+ m_new_state (new_state),
+ m_change (change),
+ m_stmt (stmt),
+ m_stmt_finder (stmt_finder),
+ m_ext_state (eg.get_ext_state ())
+{
+}
+
+impl_region_model_context::
+impl_region_model_context (program_state *state,
+ state_change *change,
+ const extrinsic_state &ext_state)
+: m_eg (NULL), m_logger (NULL), m_enode_for_diag (NULL),
+ m_old_state (NULL),
+ m_new_state (state),
+ m_change (change),
+ m_stmt (NULL),
+ m_stmt_finder (NULL),
+ m_ext_state (ext_state)
+{
+}
+
+void
+impl_region_model_context::warn (pending_diagnostic *d)
+{
+ LOG_FUNC (get_logger ());
+ if (m_eg)
+ m_eg->get_diagnostic_manager ().add_diagnostic
+ (m_enode_for_diag, m_enode_for_diag->get_supernode (),
+ m_stmt, m_stmt_finder, d);
+}
+
+void
+impl_region_model_context::remap_svalue_ids (const svalue_id_map &map)
+{
+ m_new_state->remap_svalue_ids (map);
+ if (m_change)
+ m_change->remap_svalue_ids (map);
+}
+
+int
+impl_region_model_context::on_svalue_purge (svalue_id first_unused_sid,
+ const svalue_id_map &map)
+{
+ int total = 0;
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = m_ext_state.get_sm (sm_idx);
+ total += smap->on_svalue_purge (sm, sm_idx, first_unused_sid,
+ map, this);
+ }
+ if (m_change)
+ total += m_change->on_svalue_purge (first_unused_sid);
+ return total;
+}
+
+/* class setjmp_svalue : public svalue. */
+
+/* Compare the fields of this setjmp_svalue with OTHER, returning true
+ if they are equal.
+ For use by svalue::operator==. */
+
+bool
+setjmp_svalue::compare_fields (const setjmp_svalue &other) const
+{
+ return m_enode == other.m_enode;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for setjmp_svalue. */
+
+void
+setjmp_svalue::add_to_hash (inchash::hash &hstate) const
+{
+ hstate.add_int (m_enode->m_index);
+}
+
+/* Get the index of the stored exploded_node. */
+
+int
+setjmp_svalue::get_index () const
+{
+ return m_enode->m_index;
+}
+
+/* Implementation of svalue::print_details vfunc for setjmp_svalue. */
+
+void
+setjmp_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+ svalue_id this_sid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+ pp_printf (pp, "setjmp: EN: %i", m_enode->m_index);
+}
+
+/* Concrete implementation of sm_context, wiring it up to the rest of this
+ file. */
+
+class impl_sm_context : public sm_context
+{
+public:
+ impl_sm_context (exploded_graph &eg,
+ int sm_idx,
+ const state_machine &sm,
+ const exploded_node *enode_for_diag,
+ const program_state *old_state,
+ program_state *new_state,
+ state_change *change,
+ const sm_state_map *old_smap,
+ sm_state_map *new_smap,
+ stmt_finder *stmt_finder = NULL)
+ : sm_context (sm_idx, sm),
+ m_logger (eg.get_logger ()),
+ m_eg (eg), m_enode_for_diag (enode_for_diag),
+ m_old_state (old_state), m_new_state (new_state),
+ m_change (change),
+ m_old_smap (old_smap), m_new_smap (new_smap),
+ m_stmt_finder (stmt_finder)
+ {
+ }
+
+ logger *get_logger () const { return m_logger.get_logger (); }
+
+ tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE
+ {
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+ m_change, call);
+ region_model *model = m_new_state->m_region_model;
+ return model->get_fndecl_for_call (call, &old_ctxt);
+ }
+
+ void on_transition (const supernode *node ATTRIBUTE_UNUSED,
+ const gimple *stmt ATTRIBUTE_UNUSED,
+ tree var,
+ state_machine::state_t from,
+ state_machine::state_t to,
+ tree origin) FINAL OVERRIDE
+ {
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/,
+ m_change, stmt);
+ svalue_id var_old_sid
+ = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
+
+ impl_region_model_context new_ctxt (m_eg, m_enode_for_diag,
+ m_old_state, m_new_state,
+ m_change, NULL);
+ svalue_id var_new_sid
+ = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
+ svalue_id origin_new_sid
+ = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
+
+ state_machine::state_t current = m_old_smap->get_state (var_old_sid);
+ if (current == from)
+ {
+ if (logger)
+ logger->log ("%s: state transition of %qE: %s -> %s",
+ m_sm.get_name (),
+ var,
+ m_sm.get_state_name (from),
+ m_sm.get_state_name (to));
+ m_new_smap->set_state (m_new_state->m_region_model, var_new_sid,
+ to, origin_new_sid);
+ if (m_change)
+ m_change->add_sm_change (m_sm_idx, var_new_sid, from, to);
+ }
+ }
+
+ void warn_for_state (const supernode *snode, const gimple *stmt,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d) FINAL OVERRIDE
+ {
+ LOG_FUNC (get_logger ());
+ gcc_assert (d); // take ownership
+
+ impl_region_model_context old_ctxt
+ (m_eg, m_enode_for_diag, m_old_state, m_new_state, m_change, NULL);
+ state_machine::state_t current;
+ if (var)
+ {
+ svalue_id var_old_sid
+ = m_old_state->m_region_model->get_rvalue (var, &old_ctxt);
+ current = m_old_smap->get_state (var_old_sid);
+ }
+ else
+ current = m_old_smap->get_global_state ();
+
+ if (state == current)
+ {
+ m_eg.get_diagnostic_manager ().add_diagnostic
+ (&m_sm, m_enode_for_diag, snode, stmt, m_stmt_finder,
+ var, state, d);
+ }
+ else
+ delete d;
+ }
+
+ /* Hook for picking more readable trees for SSA names of temporaries,
+ so that rather than e.g.
+ "double-free of '<unknown>'"
+ we can print:
+ "double-free of 'inbuf.data'". */
+
+ tree get_readable_tree (tree expr) FINAL OVERRIDE
+ {
+ /* Only for SSA_NAMEs of temporaries; otherwise, return EXPR, as it's
+ likely to be the least surprising tree to report. */
+ if (TREE_CODE (expr) != SSA_NAME)
+ return expr;
+ if (SSA_NAME_VAR (expr) != NULL)
+ return expr;
+
+ gcc_assert (m_new_state);
+ svalue_id sid = m_new_state->m_region_model->get_rvalue (expr, NULL);
+ /* Find trees for all regions storing the value. */
+ auto_vec<path_var> pvs;
+ m_new_state->m_region_model->get_path_vars_for_svalue (sid, &pvs);
+ if (pvs.length () < 1)
+ return expr;
+ /* Pick the "best" such tree. */
+ // TODO: should we also consider (and consolidate) equiv classes?
+ pvs.qsort (readability_comparator);
+ return pvs[0].m_tree;
+ }
+
+ state_machine::state_t get_global_state () const FINAL OVERRIDE
+ {
+ return m_old_state->m_checker_states[m_sm_idx]->get_global_state ();
+ }
+
+ void set_global_state (state_machine::state_t state) FINAL OVERRIDE
+ {
+ m_new_state->m_checker_states[m_sm_idx]->set_global_state (state);
+ }
+
+ void on_custom_transition (custom_transition *transition) FINAL OVERRIDE
+ {
+ transition->impl_transition (&m_eg,
+ const_cast<exploded_node *> (m_enode_for_diag),
+ m_sm_idx);
+ }
+
+ log_user m_logger;
+ exploded_graph &m_eg;
+ const exploded_node *m_enode_for_diag;
+ const program_state *m_old_state;
+ program_state *m_new_state;
+ state_change *m_change;
+ const sm_state_map *m_old_smap;
+ sm_state_map *m_new_smap;
+ stmt_finder *m_stmt_finder;
+};
+
+/* Subclass of stmt_finder for finding the best stmt to report the leak at,
+ given the emission path. */
+
+class leak_stmt_finder : public stmt_finder
+{
+public:
+ leak_stmt_finder (const exploded_graph &eg, tree var)
+ : m_eg (eg), m_var (var) {}
+
+ stmt_finder *clone () const FINAL OVERRIDE
+ {
+ return new leak_stmt_finder (m_eg, m_var);
+ }
+
+ const gimple *find_stmt (const exploded_path &epath)
+ FINAL OVERRIDE
+ {
+ logger * const logger = m_eg.get_logger ();
+ LOG_FUNC (logger);
+
+ if (TREE_CODE (m_var) == SSA_NAME)
+ {
+ /* Locate the final write to this SSA name in the path. */
+ const gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
+
+ int idx_of_def_stmt;
+ bool found = epath.find_stmt_backwards (def_stmt, &idx_of_def_stmt);
+ if (!found)
+ goto not_found;
+
+ /* What was the next write to the underlying var
+ after the SSA name was set? (if any). */
+
+ for (unsigned idx = idx_of_def_stmt + 1;
+ idx < epath.m_edges.length ();
+ ++idx)
+ {
+ const exploded_edge *eedge = epath.m_edges[idx];
+ if (logger)
+ logger->log ("eedge[%i]: EN %i -> EN %i",
+ idx,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ const exploded_node *dst_node = eedge->m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const gimple *stmt = dst_point.get_stmt ();
+ if (!stmt)
+ continue;
+ if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+ {
+ tree lhs = gimple_assign_lhs (assign);
+ if (TREE_CODE (lhs) == SSA_NAME
+ && SSA_NAME_VAR (lhs) == SSA_NAME_VAR (m_var))
+ return assign;
+ }
+ }
+ }
+
+ not_found:
+
+ /* Look backwards for the first statement with a location. */
+ int i;
+ const exploded_edge *eedge;
+ FOR_EACH_VEC_ELT_REVERSE (epath.m_edges, i, eedge)
+ {
+ if (logger)
+ logger->log ("eedge[%i]: EN %i -> EN %i",
+ i,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ const exploded_node *dst_node = eedge->m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const gimple *stmt = dst_point.get_stmt ();
+ if (stmt)
+ if (stmt->location != UNKNOWN_LOCATION)
+ return stmt;
+ }
+
+ gcc_unreachable ();
+ return NULL;
+ }
+
+private:
+ const exploded_graph &m_eg;
+ tree m_var;
+};
+
+/* A measurement of how good EXPR is for presenting to the user, so
+ that e.g. we can say prefer printing
+ "leak of 'tmp.m_ptr'"
+ over:
+ "leak of '<unknown>'". */
+
+static int
+readability (const_tree expr)
+{
+ gcc_assert (expr);
+ switch (TREE_CODE (expr))
+ {
+ case COMPONENT_REF:
+ case MEM_REF:
+ /* Impose a slight readability penalty relative to that of
+ operand 0. */
+ return readability (TREE_OPERAND (expr, 0)) - 1;
+
+ case SSA_NAME:
+ {
+ if (tree var = SSA_NAME_VAR (expr))
+ return readability (var);
+ /* Avoid printing '<unknown>' for SSA names for temporaries. */
+ return -1;
+ }
+ break;
+
+ case VAR_DECL:
+ /* Arbitrarily-chosen "high readability" value. */
+ return 256;
+
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+/* A qsort comparator for trees to sort them into most user-readable to
+ least user-readable. */
+
+static int
+readability_comparator (const void *p1, const void *p2)
+{
+ path_var pv1 = *(path_var const *)p1;
+ path_var pv2 = *(path_var const *)p2;
+
+ /* TODO: should we consider stack depths? */
+ int r1 = readability (pv1.m_tree);
+ int r2 = readability (pv2.m_tree);
+
+ return r2 - r1;
+}
+
+/* Create an sm_context and use it to call SM's on_leak vfunc, so that
+ it can potentially complain about a leak of DST_SID (in a new region_model)
+ in the given STATE, where MAP can be used to map SID back to an "old"
+ region_model. */
+
+void
+impl_region_model_context::on_state_leak (const state_machine &sm,
+ int sm_idx,
+ svalue_id dst_sid,
+ svalue_id first_unused_sid,
+ const svalue_id_map &map,
+ state_machine::state_t state)
+{
+ logger * const logger = get_logger ();
+ LOG_SCOPE (logger);
+ if (logger)
+ logger->log ("considering leak of sv%i", dst_sid.as_int ());
+
+ if (!m_eg)
+ return;
+
+ /* m_old_state also needs to be non-NULL so that the sm_ctxt can look
+ up the old state of the sid. */
+ gcc_assert (m_old_state);
+
+ /* Don't report on sid leaking if it's equal to one of the used sids.
+ For example, given:
+ some_non_trivial_expression = malloc (sizeof (struct foo));
+ we have:
+ _1 = malloc; (void *)
+ some_non_trivial_expression = _1; (struct foo *)
+ and at leak-detection time we may have:
+ sv5: {type: 'struct foo *', &r3} (used)
+ sv6: {type: 'void *', &r3} (unused)
+ where both point to the same region. We don't want to report a
+ leak of sv6, so we reject the report due to its equality with sv5. */
+ gcc_assert (m_new_state);
+ gcc_assert (!first_unused_sid.null_p ());
+ for (int i = 0; i < first_unused_sid.as_int (); i++)
+ {
+ svalue_id used_sid = svalue_id::from_int (i);
+
+ /* Use the "_without_cm" form of eval_condition, since
+ we're half-way through purging - we don't want to introduce new
+ equivalence classes into the constraint_manager for "sid" and
+ for each of the used_sids. */
+ const region_model &rm = *m_new_state->m_region_model;
+ tristate eq = rm.eval_condition_without_cm (dst_sid, EQ_EXPR, used_sid);
+ if (eq.is_true ())
+ {
+ if (logger)
+ logger->log ("rejecting leak of sv%i due to equality with sv%i",
+ dst_sid.as_int (), used_sid.as_int ());
+ return;
+ }
+ }
+
+ /* SID has leaked within the new state: no regions use it.
+ We need to convert it back to a tree, but since no regions use it, we
+ have to use MAP to convert it back to an svalue_id within the old state.
+ We can then look that svalue_id up to locate regions and thus tree(s)
+ that use it. */
+
+ svalue_id old_sid = map.get_src_for_dst (dst_sid);
+
+ auto_vec<path_var> leaked_pvs;
+ m_old_state->m_region_model->get_path_vars_for_svalue (old_sid, &leaked_pvs);
+
+ if (leaked_pvs.length () < 1)
+ return;
+
+ /* Find "best" leaked tree.
+ Sort the leaks into most human-readable first, through
+ to least user-readable. Given that we only emit one
+ leak per EC, this ought to ensure that we pick the most
+ user-readable description of each leaking EC.
+ This assumes that all vars in the EC have the same state. */
+ leaked_pvs.qsort (readability_comparator);
+
+ tree leaked_tree = leaked_pvs[0].m_tree;
+ if (logger)
+ logger->log ("best leaked_tree: %qE", leaked_tree);
+
+ leak_stmt_finder stmt_finder (*m_eg, leaked_tree);
+ impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
+ m_old_state, m_new_state,
+ m_change,
+ m_old_state->m_checker_states[sm_idx],
+ m_new_state->m_checker_states[sm_idx],
+ &stmt_finder);
+ gcc_assert (m_enode_for_diag);
+
+ /* Don't complain about leaks when returning from "main". */
+ if (m_enode_for_diag->get_supernode ()
+ && m_enode_for_diag->get_supernode ()->return_p ())
+ {
+ tree fndecl = m_enode_for_diag->get_function ()->decl;
+ if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), "main"))
+ {
+ if (logger)
+ logger->log ("not reporting leak from main");
+ return;
+ }
+ }
+
+ pending_diagnostic *pd = sm.on_leak (leaked_tree);
+ if (pd)
+ m_eg->get_diagnostic_manager ().add_diagnostic
+ (&sm, m_enode_for_diag, m_enode_for_diag->get_supernode (),
+ m_stmt, &stmt_finder,
+ leaked_tree, state, pd);
+}
+
+/* Implementation of region_model_context::on_inherited_svalue vfunc
+ for impl_region_model_context.
+ Notify all checkers that CHILD_SID has been created from PARENT_SID,
+ so that those state machines that inherit state can propagate the state
+ from parent to child. */
+
+void
+impl_region_model_context::on_inherited_svalue (svalue_id parent_sid,
+ svalue_id child_sid)
+{
+ if (!m_new_state)
+ return;
+
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = m_ext_state.get_sm (sm_idx);
+ if (sm.inherited_state_p ())
+ smap->on_inherited_svalue (parent_sid, child_sid);
+ }
+}
+
+/* Implementation of region_model_context::on_cast vfunc
+ for impl_region_model_context.
+ Notify all checkers that DST_SID is a cast of SRC_SID, so that sm-state
+ can be propagated from src to dst. */
+
+void
+impl_region_model_context::on_cast (svalue_id src_sid,
+ svalue_id dst_sid)
+{
+ if (!m_new_state)
+ return;
+
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+ smap->on_cast (src_sid, dst_sid);
+}
+
+/* Implementation of region_model_context::on_condition vfunc.
+ Notify all state machines about the condition, which could lead to
+ state transitions. */
+
+void
+impl_region_model_context::on_condition (tree lhs, enum tree_code op, tree rhs)
+{
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = m_ext_state.get_sm (sm_idx);
+ impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag,
+ m_old_state, m_new_state,
+ m_change,
+ m_old_state->m_checker_states[sm_idx],
+ m_new_state->m_checker_states[sm_idx]);
+ sm.on_condition (&sm_ctxt,
+ m_enode_for_diag->get_supernode (), m_stmt,
+ lhs, op, rhs);
+ }
+}
+
+/* struct point_and_state. */
+
+/* Assert that this object is sane. */
+
+void
+point_and_state::validate (const extrinsic_state &ext_state) const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ m_point.validate ();
+
+ m_state.validate (ext_state);
+
+ /* Verify that the callstring's model of the stack corresponds to that
+ of the region_model. */
+ /* They should have the same depth. */
+ gcc_assert (m_point.get_stack_depth ()
+ == m_state.m_region_model->get_stack_depth ());
+ /* Check the functions in the callstring vs those in the frames
+ at each depth. */
+ for (int depth = 0; depth < m_point.get_stack_depth (); ++depth)
+ {
+ gcc_assert (m_point.get_function_at_depth (depth)
+ == m_state.m_region_model->get_function_at_depth (depth));
+ }
+}
+
+/* Subroutine of print_enode_indices: print a run of indices from START_IDX
+ to END_IDX to PP, using and updating *FIRST_RUN. */
+
+static void
+print_run (pretty_printer *pp, int start_idx, int end_idx,
+ bool *first_run)
+{
+ if (!(*first_run))
+ pp_string (pp, ", ");
+ *first_run = false;
+ if (start_idx == end_idx)
+ pp_printf (pp, "EN: %i", start_idx);
+ else
+ pp_printf (pp, "EN: %i-%i", start_idx, end_idx);
+}
+
+/* Print the indices within ENODES to PP, collecting them as
+ runs/singletons e.g. "EN: 4-7, EN: 20-23, EN: 42". */
+
+static void
+print_enode_indices (pretty_printer *pp,
+ const auto_vec<exploded_node *> &enodes)
+{
+ int cur_start_idx = -1;
+ int cur_finish_idx = -1;
+ bool first_run = true;
+ unsigned i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (enodes, i, enode)
+ {
+ if (cur_start_idx == -1)
+ {
+ gcc_assert (cur_finish_idx == -1);
+ cur_start_idx = cur_finish_idx = enode->m_index;
+ }
+ else
+ {
+ if (enode->m_index == cur_finish_idx + 1)
+ /* Continuation of a run. */
+ cur_finish_idx = enode->m_index;
+ else
+ {
+ /* Finish existing run, start a new one. */
+ gcc_assert (cur_start_idx >= 0);
+ gcc_assert (cur_finish_idx >= 0);
+ print_run (pp, cur_start_idx, cur_finish_idx,
+ &first_run);
+ cur_start_idx = cur_finish_idx = enode->m_index;
+ }
+ }
+ }
+ /* Finish any existing run. */
+ if (cur_start_idx >= 0)
+ {
+ gcc_assert (cur_finish_idx >= 0);
+ print_run (pp, cur_start_idx, cur_finish_idx,
+ &first_run);
+ }
+}
+
+/* For use by dump_dot, get a value for the .dot "fillcolor" attribute.
+ Colorize by sm-state, to make it easier to see how sm-state propagates
+ through the exploded_graph. */
+
+const char *
+exploded_node::get_dot_fillcolor () const
+{
+ const program_state &state = get_state ();
+
+ /* We want to be able to easily distinguish the no-sm-state case,
+ and to be able to distinguish cases where there's a single state
+ from each other.
+
+ Sum the sm_states, and use the result to choose from a table,
+ modulo table-size, special-casing the "no sm-state" case. */
+ int total_sm_state = 0;
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
+ {
+ for (sm_state_map::iterator_t iter = smap->begin ();
+ iter != smap->end ();
+ ++iter)
+ total_sm_state += (*iter).second.m_state;
+ total_sm_state += smap->get_global_state ();
+ }
+
+ if (total_sm_state > 0)
+ {
+ /* An arbitrarily-picked collection of light colors. */
+ const char * const colors[]
+ = {"azure", "coral", "cornsilk", "lightblue", "yellow"};
+ const int num_colors = sizeof (colors) / sizeof (colors[0]);
+ return colors[total_sm_state % num_colors];
+ }
+ else
+ /* No sm-state. */
+ return "lightgrey";
+}
+
+/* Implementation of dnode::dump_dot vfunc for exploded_node. */
+
+void
+exploded_node::dump_dot (graphviz_out *gv, const dump_args_t &args) const
+{
+ pretty_printer *pp = gv->get_pp ();
+
+ dump_dot_id (pp);
+ pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
+ get_dot_fillcolor ());
+ pp_write_text_to_stream (pp);
+
+ pp_printf (pp, "EN: %i", m_index);
+ pp_newline (pp);
+
+ format f (true);
+ m_ps.get_point ().print (pp, f);
+ pp_newline (pp);
+
+ const extrinsic_state &ext_state = args.m_eg.get_ext_state ();
+ const program_state &state = m_ps.get_state ();
+ state.dump_to_pp (ext_state, true, pp);
+ pp_newline (pp);
+
+ {
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
+ {
+ if (!smap->is_empty_p ())
+ {
+ pp_printf (pp, "%s: ", ext_state.get_name (i));
+ smap->print (ext_state.get_sm (i), pp);
+ pp_newline (pp);
+ }
+ }
+ }
+
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
+
+ pp_string (pp, "\"];\n\n");
+ pp_flush (pp);
+}
+
+/* Dump this to PP in a form suitable for use as an id in .dot output. */
+
+void
+exploded_node::dump_dot_id (pretty_printer *pp) const
+{
+ pp_printf (pp, "exploded_node_%i", m_index);
+}
+
+/* Dump a multiline representation of this node to PP. */
+
+void
+exploded_node::dump_to_pp (pretty_printer *pp,
+ const extrinsic_state &ext_state) const
+{
+ pp_printf (pp, "EN: %i", m_index);
+ pp_newline (pp);
+
+ format f (true);
+ m_ps.get_point ().print (pp, f);
+ pp_newline (pp);
+
+ m_ps.get_state ().dump_to_pp (ext_state, false, pp);
+ pp_newline (pp);
+}
+
+/* Dump a multiline representation of this node to FILE. */
+
+void
+exploded_node::dump (FILE *fp,
+ const extrinsic_state &ext_state) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = fp;
+ dump_to_pp (&pp, ext_state);
+ pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this node to stderr. */
+
+DEBUG_FUNCTION void
+exploded_node::dump (const extrinsic_state &ext_state) const
+{
+ dump (stderr, ext_state);
+}
+
+/* Return true if FNDECL has a gimple body. */
+// TODO: is there a pre-canned way to do this?
+
+static bool
+fndecl_has_gimple_body_p (tree fndecl)
+{
+ if (fndecl == NULL_TREE)
+ return false;
+
+ cgraph_node *n = cgraph_node::get (fndecl);
+ if (!n)
+ return false;
+
+ return n->has_gimple_body_p ();
+}
+
+/* A pending_diagnostic subclass for implementing "__analyzer_dump_path". */
+
+class dump_path_diagnostic
+ : public pending_diagnostic_subclass<dump_path_diagnostic>
+{
+public:
+ bool emit (rich_location *richloc) FINAL OVERRIDE
+ {
+ inform (richloc, "path");
+ return true;
+ }
+
+ const char *get_kind () const FINAL OVERRIDE { return "dump_path_diagnostic"; }
+
+ bool operator== (const dump_path_diagnostic &) const
+ {
+ return true;
+ }
+};
+
+/* Modify STATE in place, applying the effects of the stmt at this node's
+ point. */
+
+exploded_node::on_stmt_flags
+exploded_node::on_stmt (exploded_graph &eg,
+ const supernode *snode,
+ const gimple *stmt,
+ program_state *state,
+ state_change *change) const
+{
+ /* Preserve the old state. It is used here for looking
+ up old checker states, for determining state transitions, and
+ also within impl_region_model_context and impl_sm_context for
+ going from tree to svalue_id. */
+ const program_state old_state (*state);
+
+ impl_region_model_context ctxt (eg, this,
+ &old_state, state, change,
+ stmt);
+
+ if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+ state->m_region_model->on_assignment (assign, &ctxt);
+
+ if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+ state->m_region_model->on_return (return_, &ctxt);
+
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ {
+ /* Debugging/test support. */
+ if (is_special_named_call_p (call, "__analyzer_dump", 0))
+ {
+ /* Handle the builtin "__analyzer_dump" by dumping state
+ to stderr. */
+ dump (eg.get_ext_state ());
+ }
+ else if (is_special_named_call_p (call, "__analyzer_dump_path", 0))
+ {
+ /* Handle the builtin "__analyzer_dump_path" by queuing a
+ diagnostic at this exploded_node. */
+ ctxt.warn (new dump_path_diagnostic ());
+ }
+ else if (is_special_named_call_p (call, "__analyzer_dump_region_model", 0))
+ {
+ /* Handle the builtin "__analyzer_dump_region_model" by dumping
+ the region model's state to stderr. */
+ state->m_region_model->dump (false);
+ }
+ else if (is_special_named_call_p (call, "__analyzer_eval", 1))
+ {
+ /* Handle the builtin "__analyzer_eval" by evaluating the input
+ and dumping as a dummy warning, so that test cases can use
+ dg-warning to validate the result (and so unexpected warnings will
+ lead to DejaGnu failures). */
+ tree t_arg = gimple_call_arg (call, 0);
+ tristate t
+ = state->m_region_model->eval_condition (t_arg,
+ NE_EXPR,
+ integer_zero_node,
+ &ctxt);
+ warning_at (call->location, 0, "%s", t.as_string ());
+ }
+ else if (is_special_named_call_p (call, "__analyzer_break", 0))
+ {
+ /* Handle the builtin "__analyzer_break" by triggering a
+ breakpoint. */
+ /* TODO: is there a good cross-platform way to do this? */
+ raise (SIGINT);
+ }
+ else if (is_setjmp_call_p (stmt))
+ state->m_region_model->on_setjmp (call, this, &ctxt);
+ else if (is_longjmp_call_p (call))
+ {
+ on_longjmp (eg, call, state, &ctxt);
+ return on_stmt_flags::terminate_path ();
+ }
+ else
+ state->m_region_model->on_call_pre (call, &ctxt);
+ }
+
+ bool any_sm_changes = false;
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = eg.get_ext_state ().get_sm (sm_idx);
+ const sm_state_map *old_smap
+ = old_state.m_checker_states[sm_idx];
+ sm_state_map *new_smap = state->m_checker_states[sm_idx];
+ impl_sm_context ctxt (eg, sm_idx, sm, this, &old_state, state,
+ change,
+ old_smap, new_smap);
+ /* Allow the state_machine to handle the stmt. */
+ if (!sm.on_stmt (&ctxt, snode, stmt))
+ {
+ /* For those stmts that were not handled by the state machine. */
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ {
+ tree callee_fndecl = gimple_call_fndecl (call);
+ // TODO: maybe we can be smarter about handling function pointers?
+
+ if (!fndecl_has_gimple_body_p (callee_fndecl))
+ new_smap->purge_for_unknown_fncall (eg, sm, call, callee_fndecl,
+ state->m_region_model);
+ }
+ }
+ if (*old_smap != *new_smap)
+ any_sm_changes = true;
+ }
+
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ state->m_region_model->on_call_post (call, &ctxt);
+
+ return on_stmt_flags (any_sm_changes);
+}
+
+/* Consider the effect of following superedge SUCC from this node.
+
+ Return true if it's feasible to follow the edge, or false
+ if it's infeasible.
+
+ Examples: if it's the "true" branch within
+ a CFG and we know the conditional is false, we know it's infeasible.
+ If it's one of multiple interprocedual "return" edges, then only
+ the edge back to the most recent callsite is feasible.
+
+ Update NEXT_STATE accordingly (e.g. to record that a condition was
+ true or false, or that the NULL-ness of a pointer has been checked,
+ pushing/popping stack frames, etc).
+
+ Update NEXT_POINT accordingly (updating the call string). */
+
+bool
+exploded_node::on_edge (exploded_graph &eg,
+ const superedge *succ,
+ program_point *next_point,
+ program_state *next_state,
+ state_change *change) const
+{
+ LOG_FUNC (eg.get_logger ());
+
+ if (!next_point->on_edge (eg, succ))
+ return false;
+
+ if (!next_state->on_edge (eg, *this, succ, change))
+ return false;
+
+ return true;
+}
+
+/* Verify that the stack at LONGJMP_POINT is still valid, given a call
+ to "setjmp" at SETJMP_POINT - the stack frame that "setjmp" was
+ called in must still be valid.
+
+ Caveat: this merely checks the call_strings in the points; it doesn't
+ detect the case where a frame returns and is then called again. */
+
+static bool
+valid_longjmp_stack_p (const program_point &longjmp_point,
+ const program_point &setjmp_point)
+{
+ const call_string &cs_at_longjmp = longjmp_point.get_call_string ();
+ const call_string &cs_at_setjmp = setjmp_point.get_call_string ();
+
+ if (cs_at_longjmp.length () < cs_at_setjmp.length ())
+ return false;
+
+ /* Check that the call strings match, up to the depth of the
+ setjmp point. */
+ for (unsigned depth = 0; depth < cs_at_setjmp.length (); depth++)
+ if (cs_at_longjmp[depth] != cs_at_setjmp[depth])
+ return false;
+
+ return true;
+}
+
+/* A pending_diagnostic subclass for complaining about bad longjmps,
+ where the enclosing function of the "setjmp" has returned (and thus
+ the stack frame no longer exists). */
+
+class stale_jmp_buf : public pending_diagnostic_subclass<dump_path_diagnostic>
+{
+public:
+ stale_jmp_buf (const gcall *setjmp_call, const gcall *longjmp_call)
+ : m_setjmp_call (setjmp_call), m_longjmp_call (longjmp_call)
+ {}
+
+ bool emit (rich_location *richloc) FINAL OVERRIDE
+ {
+ return warning_at
+ (richloc, OPT_Wanalyzer_stale_setjmp_buffer,
+ "%qs called after enclosing function of %qs has returned",
+ "longjmp", "setjmp");
+ }
+
+ const char *get_kind () const FINAL OVERRIDE
+ { return "stale_jmp_buf"; }
+
+ bool operator== (const stale_jmp_buf &other) const
+ {
+ return (m_setjmp_call == other.m_setjmp_call
+ && m_longjmp_call == other.m_longjmp_call);
+ }
+
+private:
+ const gcall *m_setjmp_call;
+ const gcall *m_longjmp_call;
+};
+
+/* Handle LONGJMP_CALL, a call to "longjmp".
+
+ Attempt to locate where "setjmp" was called on the jmp_buf and build an
+ exploded_node and exploded_edge to it representing a rewind to that frame,
+ handling the various kinds of failure that can occur. */
+
+void
+exploded_node::on_longjmp (exploded_graph &eg,
+ const gcall *longjmp_call,
+ program_state *new_state,
+ region_model_context *ctxt) const
+{
+ tree buf_ptr = gimple_call_arg (longjmp_call, 0);
+
+ region_model *new_region_model = new_state->m_region_model;
+ region_id buf_rid = new_region_model->deref_rvalue (buf_ptr, ctxt);
+ region *buf = new_region_model->get_region (buf_rid);
+ if (!buf)
+ return;
+
+ svalue_id buf_content_sid
+ = buf->get_value (*new_region_model, false, ctxt);
+ svalue *buf_content_sval = new_region_model->get_svalue (buf_content_sid);
+ if (!buf_content_sval)
+ return;
+ setjmp_svalue *setjmp_sval = buf_content_sval->dyn_cast_setjmp_svalue ();
+ if (!setjmp_sval)
+ return;
+
+ /* Build a custom enode and eedge for rewinding from the longjmp
+ call back to the setjmp. */
+
+ const exploded_node *enode_origin = setjmp_sval->get_exploded_node ();
+ rewind_info_t rewind_info (enode_origin);
+
+ const gcall *setjmp_call = rewind_info.get_setjmp_call ();
+ const program_point &setjmp_point = rewind_info.get_setjmp_point ();
+
+ const program_point &longjmp_point = get_point ();
+
+ /* Verify that the setjmp's call_stack hasn't been popped. */
+ if (!valid_longjmp_stack_p (longjmp_point, setjmp_point))
+ {
+ ctxt->warn (new stale_jmp_buf (setjmp_call, longjmp_call));
+ return;
+ }
+
+ gcc_assert (longjmp_point.get_stack_depth ()
+ >= setjmp_point.get_stack_depth ());
+
+ /* Update the state for use by the destination node. */
+
+ /* Stash the current number of diagnostics so that we can update
+ any that this adds to show where the longjmp is rewinding to. */
+
+ diagnostic_manager *dm = &eg.get_diagnostic_manager ();
+ unsigned prev_num_diagnostics = dm->get_num_diagnostics ();
+
+ new_region_model->on_longjmp (longjmp_call, setjmp_call,
+ setjmp_point.get_stack_depth (), ctxt);
+
+ program_point next_point
+ = program_point::after_supernode (setjmp_point.get_supernode (),
+ setjmp_point.get_call_string ());
+
+ state_change change;
+ exploded_node *next = eg.get_or_create_node (next_point, *new_state, &change);
+
+ /* Create custom exploded_edge for a longjmp. */
+ if (next)
+ {
+ exploded_edge *eedge
+ = eg.add_edge (const_cast<exploded_node *> (this), next, NULL,
+ change,
+ new rewind_info_t (enode_origin));
+
+ /* For any diagnostics that were queued here (such as leaks) we want
+ the checker_path to show the rewinding events after the "final event"
+ so that the user sees where the longjmp is rewinding to (otherwise the
+ path is meaningless).
+
+ For example, we want to emit something like:
+ | NN | {
+ | NN | longjmp (env, 1);
+ | | ~~~~~~~~~~~~~~~~
+ | | |
+ | | (10) 'ptr' leaks here; was allocated at (7)
+ | | (11) rewinding from 'longjmp' in 'inner'...
+ |
+ <-------------+
+ |
+ 'outer': event 12
+ |
+ | NN | i = setjmp(env);
+ | | ^~~~~~
+ | | |
+ | | (12) ...to 'setjmp' in 'outer' (saved at (2))
+
+ where the "final" event above is event (10), but we want to append
+ events (11) and (12) afterwards.
+
+ Do this by setting m_trailing_eedge on any diagnostics that were
+ just saved. */
+ unsigned num_diagnostics = dm->get_num_diagnostics ();
+ for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++)
+ {
+ saved_diagnostic *sd = dm->get_saved_diagnostic (i);
+ sd->m_trailing_eedge = eedge;
+ }
+ }
+}
+
+/* Subroutine of exploded_graph::process_node for finding the successors
+ of the supernode for a function exit basic block.
+
+ Ensure that pop_frame is called, potentially queuing diagnostics about
+ leaks. */
+
+void
+exploded_node::detect_leaks (exploded_graph &eg) const
+{
+ LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
+
+ gcc_assert (get_point ().get_supernode ()->return_p ());
+
+ /* If we're not a "top-level" function, do nothing; pop_frame
+ will be called when handling the return superedge. */
+ if (get_point ().get_stack_depth () > 1)
+ return;
+
+ /* We have a "top-level" function. */
+ gcc_assert (get_point ().get_stack_depth () == 1);
+
+ const program_state &old_state = get_state ();
+
+ /* Work with a temporary copy of the state: pop the frame, and see
+ what leaks (via purge_unused_svalues). */
+ program_state new_state (old_state);
+
+ gcc_assert (new_state.m_region_model);
+
+ purge_stats stats;
+ impl_region_model_context ctxt (eg, this,
+ &old_state, &new_state,
+ NULL,
+ get_stmt ());
+ new_state.m_region_model->pop_frame (true, &stats, &ctxt);
+}
+
+/* Dump the successors and predecessors of this enode to OUTF. */
+
+void
+exploded_node::dump_succs_and_preds (FILE *outf) const
+{
+ unsigned i;
+ exploded_edge *e;
+ {
+ auto_vec<exploded_node *> preds (m_preds.length ());
+ FOR_EACH_VEC_ELT (m_preds, i, e)
+ preds.quick_push (e->m_src);
+ pretty_printer pp;
+ print_enode_indices (&pp, preds);
+ fprintf (outf, "preds: %s\n",
+ pp_formatted_text (&pp));
+ }
+ {
+ auto_vec<exploded_node *> succs (m_succs.length ());
+ FOR_EACH_VEC_ELT (m_succs, i, e)
+ succs.quick_push (e->m_dest);
+ pretty_printer pp;
+ print_enode_indices (&pp, succs);
+ fprintf (outf, "succs: %s\n",
+ pp_formatted_text (&pp));
+ }
+}
+
+/* class rewind_info_t : public exploded_edge::custom_info_t. */
+
+/* Implementation of exploded_edge::custom_info_t::update_model vfunc
+ for rewind_info_t.
+
+ Update state for the special-case of a rewind of a longjmp
+ to a setjmp (which doesn't have a superedge, but does affect
+ state). */
+
+void
+rewind_info_t::update_model (region_model *model,
+ const exploded_edge &eedge)
+{
+ const exploded_node &src_enode = *eedge.m_src;
+ const program_point &src_point = src_enode.get_point ();
+
+ const gimple *last_stmt
+ = src_point.get_supernode ()->get_last_stmt ();
+ gcc_assert (last_stmt);
+ const gcall *longjmp_call = as_a <const gcall *> (last_stmt);
+
+ const program_point &longjmp_point = eedge.m_src->get_point ();
+ const program_point &setjmp_point = eedge.m_dest->get_point ();
+
+ gcc_assert (longjmp_point.get_stack_depth ()
+ >= setjmp_point.get_stack_depth ());
+
+ model->on_longjmp (longjmp_call,
+ get_setjmp_call (),
+ setjmp_point.get_stack_depth (), NULL);
+}
+
+/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc
+ for rewind_info_t. */
+
+void
+rewind_info_t::add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge)
+{
+ const exploded_node *src_node = eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ const exploded_node *dst_node = eedge.m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+
+ emission_path->add_event
+ (new rewind_from_longjmp_event
+ (&eedge, src_point.get_supernode ()->get_end_location (),
+ src_point.get_fndecl (),
+ src_stack_depth));
+ emission_path->add_event
+ (new rewind_to_setjmp_event
+ (&eedge, get_setjmp_call ()->location,
+ dst_point.get_fndecl (),
+ dst_stack_depth, this));
+}
+
+/* class exploded_edge : public dedge. */
+
+/* exploded_edge's ctor. */
+
+exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest,
+ const superedge *sedge,
+ const state_change &change,
+ custom_info_t *custom_info)
+: dedge (src, dest), m_sedge (sedge), m_change (change),
+ m_custom_info (custom_info)
+{
+ change.validate (dest->get_state ());
+}
+
+/* exploded_edge's dtor. */
+
+exploded_edge::~exploded_edge ()
+{
+ delete m_custom_info;
+}
+
+/* Implementation of dedge::dump_dot vfunc for exploded_edge.
+ Use the label of the underlying superedge, if any. */
+
+void
+exploded_edge::dump_dot (graphviz_out *gv, const dump_args_t &args) const
+{
+ pretty_printer *pp = gv->get_pp ();
+
+ const char *style = "\"solid,bold\"";
+ const char *color = "black";
+ int weight = 10;
+ const char *constraint = "true";
+
+ if (m_sedge)
+ switch (m_sedge->m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case SUPEREDGE_CFG_EDGE:
+ break;
+ case SUPEREDGE_CALL:
+ color = "red";
+ //constraint = "false";
+ break;
+ case SUPEREDGE_RETURN:
+ color = "green";
+ //constraint = "false";
+ break;
+ case SUPEREDGE_INTRAPROCEDURAL_CALL:
+ style = "\"dotted\"";
+ break;
+ }
+ if (m_custom_info)
+ {
+ color = "red";
+ style = "\"dotted\"";
+ }
+
+ m_src->dump_dot_id (pp);
+ pp_string (pp, " -> ");
+ m_dest->dump_dot_id (pp);
+ pp_printf (pp,
+ (" [style=%s, color=%s, weight=%d, constraint=%s,"
+ " headlabel=\""),
+ style, color, weight, constraint);
+
+ if (m_sedge)
+ m_sedge->dump_label_to_pp (pp, false);
+ else if (m_custom_info)
+ m_custom_info->print (pp);
+
+ m_change.dump (pp, args.m_eg.get_ext_state ());
+ //pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+
+ pp_printf (pp, "\"];\n");
+}
+
+/* struct stats. */
+
+/* stats' ctor. */
+
+stats::stats (int num_supernodes)
+: m_node_reuse_count (0),
+ m_node_reuse_after_merge_count (0),
+ m_num_supernodes (num_supernodes)
+{
+ for (int i = 0; i < NUM_POINT_KINDS; i++)
+ m_num_nodes[i] = 0;
+}
+
+/* Log these stats in multiline form to LOGGER. */
+
+void
+stats::log (logger *logger) const
+{
+ gcc_assert (logger);
+ for (int i = 0; i < NUM_POINT_KINDS; i++)
+ logger->log ("m_num_nodes[%s]: %i",
+ point_kind_to_string (static_cast <enum point_kind> (i)),
+ m_num_nodes[i]);
+ logger->log ("m_node_reuse_count: %i", m_node_reuse_count);
+ logger->log ("m_node_reuse_after_merge_count: %i",
+ m_node_reuse_after_merge_count);
+}
+
+/* Dump these stats in multiline form to OUT. */
+
+void
+stats::dump (FILE *out) const
+{
+ for (int i = 0; i < NUM_POINT_KINDS; i++)
+ fprintf (out, "m_num_nodes[%s]: %i\n",
+ point_kind_to_string (static_cast <enum point_kind> (i)),
+ m_num_nodes[i]);
+ fprintf (out, "m_node_reuse_count: %i\n", m_node_reuse_count);
+ fprintf (out, "m_node_reuse_after_merge_count: %i\n",
+ m_node_reuse_after_merge_count);
+
+ if (m_num_supernodes > 0)
+ fprintf (out, "PK_AFTER_SUPERNODE nodes per supernode: %.2f\n",
+ (float)m_num_nodes[PK_AFTER_SUPERNODE] / (float)m_num_supernodes);
+}
+
+/* strongly_connected_components's ctor. Tarjan's SCC algorithm. */
+
+strongly_connected_components::
+strongly_connected_components (const supergraph &sg, logger *logger)
+: m_sg (sg), m_per_node (m_sg.num_nodes ())
+{
+ LOG_SCOPE (logger);
+ auto_timevar tv (TV_ANALYZER_SCC);
+
+ for (int i = 0; i < m_sg.num_nodes (); i++)
+ m_per_node.quick_push (per_node_data ());
+
+ for (int i = 0; i < m_sg.num_nodes (); i++)
+ if (m_per_node[i].m_index == -1)
+ strong_connect (i);
+
+ if (0)
+ dump ();
+}
+
+/* Dump this object to stderr. */
+
+DEBUG_FUNCTION void
+strongly_connected_components::dump () const
+{
+ for (int i = 0; i < m_sg.num_nodes (); i++)
+ {
+ const per_node_data &v = m_per_node[i];
+ fprintf (stderr, "SN %i: index: %i lowlink: %i on_stack: %i\n",
+ i, v.m_index, v.m_lowlink, v.m_on_stack);
+ }
+}
+
+/* Subroutine of strongly_connected_components's ctor, part of Tarjan's
+ SCC algorithm. */
+
+void
+strongly_connected_components::strong_connect (unsigned index)
+{
+ supernode *v_snode = m_sg.get_node_by_index (index);
+
+ /* Set the depth index for v to the smallest unused index. */
+ per_node_data *v = &m_per_node[index];
+ v->m_index = index;
+ v->m_lowlink = index;
+ m_stack.safe_push (index);
+ v->m_on_stack = true;
+ index++;
+
+ /* Consider successors of v. */
+ unsigned i;
+ superedge *sedge;
+ FOR_EACH_VEC_ELT (v_snode->m_succs, i, sedge)
+ {
+ supernode *w_snode = sedge->m_dest;
+ per_node_data *w = &m_per_node[w_snode->m_index];
+ if (w->m_index == -1)
+ {
+ /* Successor w has not yet been visited; recurse on it. */
+ strong_connect (w_snode->m_index);
+ v->m_lowlink = MIN (v->m_lowlink, w->m_lowlink);
+ }
+ else if (w->m_on_stack)
+ {
+ /* Successor w is in stack S and hence in the current SCC
+ If w is not on stack, then (v, w) is a cross-edge in the DFS
+ tree and must be ignored. */
+ v->m_lowlink = MIN (v->m_lowlink, w->m_index);
+ }
+ }
+
+ /* If v is a root node, pop the stack and generate an SCC. */
+
+ if (v->m_lowlink == v->m_index)
+ {
+ per_node_data *w;
+ do {
+ int idx = m_stack.pop ();
+ w = &m_per_node[idx];
+ w->m_on_stack = false;
+ } while (w != v);
+ }
+}
+
+/* worklist's ctor. */
+
+worklist::worklist (const exploded_graph &eg, const analysis_plan &plan)
+: m_eg (eg),
+ m_scc (eg.get_supergraph (), eg.get_logger ()),
+ m_plan (plan),
+ m_queue (key_t (*this, NULL))
+{
+}
+
+/* Return the number of nodes in the worklist. */
+
+unsigned
+worklist::length () const
+{
+ return m_queue.nodes ();
+}
+
+/* Return the next node in the worklist, removing it. */
+
+exploded_node *
+worklist::take_next ()
+{
+ return m_queue.extract_min ();
+}
+
+/* Return the next node in the worklist without removing it. */
+
+exploded_node *
+worklist::peek_next ()
+{
+ return m_queue.min ();
+}
+
+/* Add ENODE to the worklist. */
+
+void
+worklist::add_node (exploded_node *enode)
+{
+ m_queue.insert (key_t (*this, enode), enode);
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+ Return negative if KA is before KB
+ Return positive if KA is after KB
+ Return 0 if they are equal. */
+
+int
+worklist::key_t::cmp_1 (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+ const program_point &point_a = ka.m_enode->get_point ();
+ const program_point &point_b = kb.m_enode->get_point ();
+ const call_string &call_string_a = point_a.get_call_string ();
+ const call_string &call_string_b = point_b.get_call_string ();
+
+ /* Order empty-callstring points with different functions based on the
+ analysis_plan, so that we generate summaries before they are used. */
+ if (flag_analyzer_call_summaries
+ && call_string_a.empty_p ()
+ && call_string_b.empty_p ()
+ && point_a.get_function () != NULL
+ && point_b.get_function () != NULL
+ && point_a.get_function () != point_b.get_function ())
+ {
+ return ka.m_worklist.m_plan.cmp_function (point_a.get_function (),
+ point_b.get_function ());
+ }
+
+ /* First, order by SCC. */
+ int scc_id_a = ka.get_scc_id (ka.m_enode);
+ int scc_id_b = kb.get_scc_id (kb.m_enode);
+ if (scc_id_a != scc_id_b)
+ return scc_id_a - scc_id_b;
+
+ /* If in same SCC, order by supernode index (an arbitrary but stable
+ ordering). */
+ const supernode *snode_a = ka.m_enode->get_supernode ();
+ const supernode *snode_b = kb.m_enode->get_supernode ();
+ if (snode_a == NULL)
+ {
+ if (snode_b != NULL)
+ /* One is NULL. */
+ return -1;
+ else
+ /* Both are NULL. */
+ return 0;
+ }
+ if (snode_b == NULL)
+ /* One is NULL. */
+ return 1;
+ /* Neither are NULL. */
+ gcc_assert (snode_a && snode_b);
+ if (snode_a->m_index != snode_b->m_index)
+ return snode_a->m_index - snode_b->m_index;
+
+ gcc_assert (snode_a == snode_b);
+
+ /* Order within supernode via program point. */
+ int within_snode_cmp
+ = function_point::cmp_within_supernode (point_a.get_function_point (),
+ point_b.get_function_point ());
+ if (within_snode_cmp)
+ return within_snode_cmp;
+
+ /* The points might vary by callstring; try sorting by callstring. */
+ int cs_cmp = call_string::cmp (call_string_a, call_string_b);
+ if (cs_cmp)
+ return cs_cmp;
+
+ /* Otherwise, we ought to have the same program_point. */
+ gcc_assert (point_a == point_b);
+
+ const program_state &state_a = ka.m_enode->get_state ();
+ const program_state &state_b = kb.m_enode->get_state ();
+
+ /* Sort by sm-state, so that identical sm-states are grouped
+ together in the worklist.
+ For now, sort by the hash value (might not be deterministic). */
+ for (unsigned sm_idx = 0; sm_idx < state_a.m_checker_states.length ();
+ ++sm_idx)
+ {
+ sm_state_map *smap_a = state_a.m_checker_states[sm_idx];
+ sm_state_map *smap_b = state_b.m_checker_states[sm_idx];
+
+ int sm_cmp = smap_a->hash () - smap_b->hash ();
+ if (sm_cmp)
+ return sm_cmp;
+ }
+
+ /* Otherwise, we have two enodes at the same program point but with
+ different states. We don't have a good total ordering on states,
+ so order them by enode index, so that we have at least have a
+ stable sort. */
+ return ka.m_enode->m_index - kb.m_enode->m_index;
+}
+
+/* Comparator for implementing worklist::key_t comparison operators.
+ Return negative if KA is before KB
+ Return positive if KA is after KB
+ Return 0 if they are equal. */
+
+int
+worklist::key_t::cmp (const worklist::key_t &ka, const worklist::key_t &kb)
+{
+ int result = cmp_1 (ka, kb);
+
+ /* Check that the ordering is symmetric */
+#if CHECKING_P
+ int reversed = cmp_1 (kb, ka);
+ gcc_assert (reversed == -result);
+#endif
+
+ /* We should only have 0 for equal (point, state) pairs. */
+ gcc_assert (result != 0
+ || (*ka.m_enode->get_ps_key ()
+ == *kb.m_enode->get_ps_key ()));
+
+ return result;
+}
+
+/* exploded_graph's ctor. */
+
+exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
+ const extrinsic_state &ext_state,
+ const state_purge_map *purge_map,
+ const analysis_plan &plan,
+ int verbosity)
+: m_sg (sg), m_logger (logger),
+ m_worklist (*this, plan),
+ m_ext_state (ext_state),
+ m_purge_map (purge_map),
+ m_plan (plan),
+ m_diagnostic_manager (logger, verbosity),
+ m_global_stats (m_sg.num_nodes ()),
+ m_functionless_stats (m_sg.num_nodes ()),
+ m_PK_AFTER_SUPERNODE_per_snode (m_sg.num_nodes ())
+{
+ m_origin = get_or_create_node (program_point (function_point (NULL, NULL,
+ 0, PK_ORIGIN),
+ call_string ()),
+ program_state (ext_state), NULL);
+ for (int i = 0; i < m_sg.num_nodes (); i++)
+ m_PK_AFTER_SUPERNODE_per_snode.quick_push (i);
+}
+
+/* exploded_graph's dtor. */
+
+exploded_graph::~exploded_graph ()
+{
+ for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+ iter != m_per_function_stats.end ();
+ ++iter)
+ delete (*iter).second;
+
+ for (point_map_t::iterator iter = m_per_point_data.begin ();
+ iter != m_per_point_data.end ();
+ ++iter)
+ delete (*iter).second;
+}
+
+/* Ensure that there is an exploded_node representing an external call to
+ FUN, adding it to the worklist if creating it.
+
+ Add an edge from the origin exploded_node to the function entrypoint
+ exploded_node.
+
+ Return the exploded_node for the entrypoint to the function. */
+
+exploded_node *
+exploded_graph::add_function_entry (function *fun)
+{
+ program_point point = program_point::from_function_entry (m_sg, fun);
+ program_state state (m_ext_state);
+ state.m_region_model->push_frame (fun, NULL, NULL);
+
+ exploded_node *enode = get_or_create_node (point, state, NULL);
+ /* We should never fail to add such a node. */
+ gcc_assert (enode);
+ state_change change;
+ add_edge (m_origin, enode, NULL, change);
+ return enode;
+}
+
+/* Get or create an exploded_node for (POINT, STATE).
+ If a new node is created, it is added to the worklist.
+ If CHANGE is non-NULL, use it to suppress some purging of state,
+ to make generation of state_change_event instances easier. */
+
+exploded_node *
+exploded_graph::get_or_create_node (const program_point &point,
+ const program_state &state,
+ state_change *change)
+{
+ logger * const logger = get_logger ();
+ LOG_FUNC (logger);
+ if (logger)
+ {
+ format f (false);
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ pp_string (pp, "point: ");
+ point.print (pp, f);
+ logger->end_log_line ();
+ logger->start_log_line ();
+ pp_string (pp, "state: ");
+ state.dump (m_ext_state, true);
+ logger->end_log_line ();
+ }
+
+ auto_cfun sentinel (point.get_function ());
+
+ state.validate (get_ext_state ());
+
+ //state.dump (get_ext_state ());
+
+ /* Prune state to try to improve the chances of a cache hit,
+ avoiding generating redundant nodes. */
+ program_state pruned_state = state.prune_for_point (*this, point, change);
+
+ pruned_state.validate (get_ext_state ());
+
+ //pruned_state.dump (get_ext_state ());
+
+ if (logger)
+ {
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ pp_string (pp, "pruned_state: ");
+ pruned_state.dump_to_pp (m_ext_state, true, pp);
+ logger->end_log_line ();
+ pruned_state.m_region_model->dump_to_pp (logger->get_printer (), true);
+ }
+
+ stats *per_fn_stats = get_or_create_function_stats (point.get_function ());
+
+ stats *per_cs_stats
+ = &get_or_create_per_call_string_data (point.get_call_string ())->m_stats;
+
+ point_and_state ps (point, pruned_state);
+ ps.validate (m_ext_state);
+ if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+ {
+ /* An exploded_node for PS already exists. */
+ if (logger)
+ logger->log ("reused EN: %i", (*slot)->m_index);
+ m_global_stats.m_node_reuse_count++;
+ per_fn_stats->m_node_reuse_count++;
+ per_cs_stats->m_node_reuse_count++;
+ return *slot;
+ }
+
+ per_program_point_data *per_point_data
+ = get_or_create_per_program_point_data (point);
+
+ /* Consider merging state with another enode at this program_point. */
+ if (flag_analyzer_state_merge)
+ {
+ exploded_node *existing_enode;
+ unsigned i;
+ FOR_EACH_VEC_ELT (per_point_data->m_enodes, i, existing_enode)
+ {
+ if (logger)
+ logger->log ("considering merging with existing EN: %i for point",
+ existing_enode->m_index);
+ gcc_assert (existing_enode->get_point () == point);
+ const program_state &existing_state = existing_enode->get_state ();
+
+ /* This merges successfully within the loop. */
+
+ program_state merged_state (m_ext_state);
+ if (pruned_state.can_merge_with_p (existing_state, m_ext_state,
+ &merged_state))
+ {
+ if (logger)
+ logger->log ("merging new state with that of EN: %i",
+ existing_enode->m_index);
+
+ /* Try again for a cache hit. */
+ ps.set_state (merged_state);
+ if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+ {
+ /* An exploded_node for PS already exists. */
+ if (logger)
+ logger->log ("reused EN: %i", (*slot)->m_index);
+ m_global_stats.m_node_reuse_after_merge_count++;
+ per_fn_stats->m_node_reuse_after_merge_count++;
+ per_cs_stats->m_node_reuse_after_merge_count++;
+ return *slot;
+ }
+
+ /* Otherwise, continue, using the merged state in "ps".
+ Given that merged_state's svalue_ids have no relationship
+ to those of the input state, and thus to those of CHANGE,
+ purge any svalue_ids from *CHANGE. */
+ if (change)
+ change->on_svalue_purge (svalue_id::from_int (0));
+ }
+ else
+ if (logger)
+ logger->log ("not merging new state with that of EN: %i",
+ existing_enode->m_index);
+ }
+ }
+
+ /* Impose a limit on the number of enodes per program point, and
+ simply stop if we exceed it. */
+ if ((int)per_point_data->m_enodes.length ()
+ > param_analyzer_max_enodes_per_program_point)
+ {
+ if (logger)
+ logger->log ("not creating enode; too many at program point");
+ warning_at (point.get_location (), OPT_Wanalyzer_too_complex,
+ "terminating analysis for this program point");
+ return NULL;
+ }
+
+ ps.validate (m_ext_state);
+
+ /* An exploded_node for "ps" doesn't already exist; create one. */
+ exploded_node *node = new exploded_node (ps, m_nodes.length ());
+ add_node (node);
+ m_point_and_state_to_node.put (node->get_ps_key (), node);
+
+ /* Update per-program_point data. */
+ per_point_data->m_enodes.safe_push (node);
+
+ const enum point_kind node_pk = node->get_point ().get_kind ();
+ m_global_stats.m_num_nodes[node_pk]++;
+ per_fn_stats->m_num_nodes[node_pk]++;
+ per_cs_stats->m_num_nodes[node_pk]++;
+
+ if (node_pk == PK_AFTER_SUPERNODE)
+ m_PK_AFTER_SUPERNODE_per_snode[point.get_supernode ()->m_index]++;
+
+ if (logger)
+ {
+ format f (false);
+ pretty_printer *pp = logger->get_printer ();
+ logger->log ("created EN: %i", node->m_index);
+ logger->start_log_line ();
+ pp_string (pp, "point: ");
+ point.print (pp, f);
+ logger->end_log_line ();
+ logger->start_log_line ();
+ pp_string (pp, "pruned_state: ");
+ pruned_state.dump_to_pp (m_ext_state, true, pp);
+ logger->end_log_line ();
+ }
+
+ /* Add the new node to the worlist. */
+ m_worklist.add_node (node);
+ return node;
+}
+
+/* Add an exploded_edge from SRC to DEST, recording its association
+ with SEDGE (which may be NULL), and, if non-NULL, taking ownership
+ of REWIND_INFO.
+ Return the newly-created eedge. */
+
+exploded_edge *
+exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
+ const superedge *sedge,
+ const state_change &change,
+ exploded_edge::custom_info_t *custom_info)
+{
+ exploded_edge *e = new exploded_edge (src, dest, sedge, change, custom_info);
+ digraph::add_edge (e);
+ return e;
+}
+
+/* Ensure that this graph has per-program_point-data for POINT;
+ borrow a pointer to it. */
+
+per_program_point_data *
+exploded_graph::
+get_or_create_per_program_point_data (const program_point &point)
+{
+ if (per_program_point_data **slot = m_per_point_data.get (&point))
+ return *slot;
+
+ per_program_point_data *per_point_data = new per_program_point_data (point);
+ m_per_point_data.put (&per_point_data->m_key, per_point_data);
+ return per_point_data;
+}
+
+/* Ensure that this graph has per-call_string-data for CS;
+ borrow a pointer to it. */
+
+per_call_string_data *
+exploded_graph::get_or_create_per_call_string_data (const call_string &cs)
+{
+ if (per_call_string_data **slot = m_per_call_string_data.get (&cs))
+ return *slot;
+
+ per_call_string_data *data = new per_call_string_data (cs, m_sg.num_nodes ());
+ m_per_call_string_data.put (&data->m_key,
+ data);
+ return data;
+}
+
+/* Ensure that this graph has per-function-data for FUN;
+ borrow a pointer to it. */
+
+per_function_data *
+exploded_graph::get_or_create_per_function_data (function *fun)
+{
+ if (per_function_data **slot = m_per_function_data.get (fun))
+ return *slot;
+
+ per_function_data *data = new per_function_data ();
+ m_per_function_data.put (fun, data);
+ return data;
+}
+
+/* Get this graph's per-function-data for FUN if there is any,
+ otherwise NULL. */
+
+per_function_data *
+exploded_graph::get_per_function_data (function *fun) const
+{
+ if (per_function_data **slot
+ = const_cast <per_function_data_t &> (m_per_function_data).get (fun))
+ return *slot;
+
+ return NULL;
+}
+
+/* Return true if NODE and FUN should be traversed directly, rather than
+ called via other functions. */
+
+static bool
+toplevel_function_p (cgraph_node *node, function *fun, logger *logger)
+{
+ /* TODO: better logic here
+ e.g. only if more than one caller, and significantly complicated.
+ Perhaps some whole-callgraph analysis to decide if it's worth summarizing
+ an edge, and if so, we need summaries. */
+ if (flag_analyzer_call_summaries)
+ {
+ int num_call_sites = 0;
+ for (cgraph_edge *edge = node->callers; edge; edge = edge->next_caller)
+ ++num_call_sites;
+
+ /* For now, if there's more than one in-edge, and we want call
+ summaries, do it at the top level so that there's a chance
+ we'll have a summary when we need one. */
+ if (num_call_sites > 1)
+ {
+ if (logger)
+ logger->log ("traversing %qE (%i call sites)",
+ fun->decl, num_call_sites);
+ return true;
+ }
+ }
+
+ if (!TREE_PUBLIC (fun->decl))
+ {
+ if (logger)
+ logger->log ("not traversing %qE (static)", fun->decl);
+ return false;
+ }
+
+ if (logger)
+ logger->log ("traversing %qE (all checks passed)", fun->decl);
+
+ return true;
+}
+
+/* Add initial nodes to EG, with entrypoints for externally-callable
+ functions. */
+
+void
+exploded_graph::build_initial_worklist ()
+{
+ logger * const logger = get_logger ();
+ LOG_SCOPE (logger);
+
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ {
+ function *fun = node->get_fun ();
+ if (!toplevel_function_p (node, fun, logger))
+ continue;
+ exploded_node *enode = add_function_entry (fun);
+ if (logger)
+ logger->log ("created EN %i for %qE entrypoint",
+ enode->m_index, fun->decl);
+ }
+}
+
+/* The main loop of the analysis.
+ Take freshly-created exploded_nodes from the worklist, calling
+ process_node on them to explore the <point, state> graph.
+ Add edges to their successors, potentially creating new successors
+ (which are also added to the worklist). */
+
+void
+exploded_graph::process_worklist ()
+{
+ logger * const logger = get_logger ();
+ LOG_SCOPE (logger);
+ auto_timevar tv (TV_ANALYZER_WORKLIST);
+
+ while (m_worklist.length () > 0)
+ {
+ exploded_node *node = m_worklist.take_next ();
+ gcc_assert (node->m_succs.length () == 0
+ || node == m_origin);
+
+ if (logger)
+ logger->log ("next to process: EN: %i", node->m_index);
+
+ /* Avoid exponential explosions of nodes by attempting to merge
+ nodes that are at the same program point and which have
+ sufficiently similar state. */
+ if (flag_analyzer_state_merge && node != m_origin)
+ if (exploded_node *node_2 = m_worklist.peek_next ())
+ {
+ gcc_assert (node->m_succs.length () == 0);
+ gcc_assert (node_2->m_succs.length () == 0);
+
+ gcc_assert (node != node_2);
+
+ if (logger)
+ logger->log ("peek worklist: EN: %i", node_2->m_index);
+
+ if (node->get_point () == node_2->get_point ())
+ {
+ if (logger)
+ {
+ format f (false);
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ logger->log_partial
+ ("got potential merge EN: %i and EN: %i at ",
+ node->m_index, node_2->m_index);
+ node->get_point ().print (pp, f);
+ logger->end_log_line ();
+ }
+
+ const program_state &state = node->get_state ();
+ const program_state &state_2 = node_2->get_state ();
+
+ /* They shouldn't be equal, or we wouldn't have two
+ separate nodes. */
+ gcc_assert (state != state_2);
+
+ program_state merged_state (m_ext_state);
+ state_change change;
+ if (state.can_merge_with_p (state_2, m_ext_state,
+ &merged_state))
+ {
+ if (logger)
+ logger->log ("merging EN: %i and EN: %i",
+ node->m_index, node_2->m_index);
+
+ if (merged_state == state)
+ {
+ /* Then merge node_2 into node by adding an edge. */
+ add_edge (node_2, node, NULL, change);
+
+ /* Remove node_2 from the worklist. */
+ m_worklist.take_next ();
+
+ /* Continue processing "node" below. */
+ }
+ else if (merged_state == state_2)
+ {
+ /* Then merge node into node_2, and leave node_2
+ in the worklist, to be processed on the next
+ iteration. */
+ add_edge (node, node_2, NULL, change);
+ continue;
+ }
+ else
+ {
+ /* We have a merged state that differs from
+ both state and state_2. */
+
+ /* Remove node_2 from the worklist. */
+ m_worklist.take_next ();
+
+ /* Create (or get) an exploded node for the merged
+ states, adding to the worklist. */
+ exploded_node *merged_enode
+ = get_or_create_node (node->get_point (),
+ merged_state, &change);
+ if (merged_enode == NULL)
+ continue;
+
+ if (logger)
+ logger->log ("merged EN: %i and EN: %i into EN: %i",
+ node->m_index, node_2->m_index,
+ merged_enode->m_index);
+
+ /* "node" and "node_2" have both now been removed
+ from the worklist; we should not process them.
+
+ "merged_enode" may be a new node; if so it will be
+ processed in a subsequent iteration.
+ Alternatively, "merged_enode" could be an existing
+ node; one way the latter can
+ happen is if we end up merging a succession of
+ similar nodes into one. */
+
+ /* If merged_node is one of the two we were merging,
+ add it back to the worklist to ensure it gets
+ processed.
+
+ Add edges from the merged nodes to it (but not a
+ self-edge). */
+ if (merged_enode == node)
+ m_worklist.add_node (merged_enode);
+ else
+ add_edge (node, merged_enode, NULL, change);
+
+ if (merged_enode == node_2)
+ m_worklist.add_node (merged_enode);
+ else
+ add_edge (node_2, merged_enode, NULL, change);
+
+ continue;
+ }
+ }
+
+ /* TODO: should we attempt more than two nodes,
+ or just do pairs of nodes? (and hope that we get
+ a cascade of mergers). */
+ }
+ }
+
+ process_node (node);
+
+ /* Impose a hard limit on the number of exploded nodes, to ensure
+ that the analysis terminates in the face of pathological state
+ explosion (or bugs).
+
+ Specifically, the limit is on the number of PK_AFTER_SUPERNODE
+ exploded nodes, looking at supernode exit events.
+
+ We use exit rather than entry since there can be multiple
+ entry ENs, one per phi; the number of PK_AFTER_SUPERNODE ought
+ to be equivalent to the number of supernodes multiplied by the
+ number of states. */
+ const int limit = m_sg.num_nodes () * param_analyzer_bb_explosion_factor;
+ if (m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE] > limit)
+ {
+ if (logger)
+ logger->log ("bailing out; too many nodes");
+ warning_at (node->get_point ().get_location (),
+ OPT_Wanalyzer_too_complex,
+ "analysis bailed out early"
+ " (%i 'after-snode' enodes; %i enodes)",
+ m_global_stats.m_num_nodes[PK_AFTER_SUPERNODE],
+ m_nodes.length ());
+ return;
+ }
+ }
+}
+
+/* Return true if STMT must appear at the start of its exploded node, and
+ thus we can't consolidate its effects within a run of other statements,
+ where PREV_STMT was the previous statement. */
+
+static bool
+stmt_requires_new_enode_p (const gimple *stmt,
+ const gimple *prev_stmt)
+{
+ /* Stop consolidating at calls to
+ "__analyzer_dump_exploded_nodes", so they always appear at the
+ start of an exploded_node. */
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
+ 1))
+ return true;
+
+ /* If we had a PREV_STMT with an unknown location, and this stmt
+ has a known location, then if a state change happens here, it
+ could be consolidated into PREV_STMT, giving us an event with
+ no location. Ensure that STMT gets its own exploded_node to
+ avoid this. */
+ if (prev_stmt->location == UNKNOWN_LOCATION
+ && stmt->location != UNKNOWN_LOCATION)
+ return true;
+
+ return false;
+}
+
+/* The core of exploded_graph::process_worklist (the main analysis loop),
+ handling one node in the worklist.
+
+ Get successor <point, state> pairs for NODE, calling get_or_create on
+ them, and adding an exploded_edge to each successors.
+
+ Freshly-created nodes will be added to the worklist. */
+
+void
+exploded_graph::process_node (exploded_node *node)
+{
+ logger * const logger = get_logger ();
+ LOG_FUNC_1 (logger, "EN: %i", node->m_index);
+
+ const program_point &point = node->get_point ();
+
+ /* Update cfun and input_location in case of an ICE: make it easier to
+ track down which source construct we're failing to handle. */
+ auto_cfun sentinel (node->get_function ());
+ const gimple *stmt = point.get_stmt ();
+ if (stmt)
+ input_location = stmt->location;
+
+ const program_state &state = node->get_state ();
+ if (logger)
+ {
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ pp_string (pp, "point: ");
+ point.print (pp, format (false));
+ pp_string (pp, ", state: ");
+ state.dump_to_pp (m_ext_state, true, pp);
+ logger->end_log_line ();
+ }
+
+ switch (point.get_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case PK_ORIGIN:
+ /* This node exists to simplify finding the shortest path
+ to an exploded_node. */
+ break;
+
+ case PK_BEFORE_SUPERNODE:
+ {
+ program_state next_state (state);
+ state_change change;
+
+ if (point.get_from_edge ())
+ {
+ impl_region_model_context ctxt (*this, node,
+ &state, &next_state, &change,
+ NULL);
+ const cfg_superedge *last_cfg_superedge
+ = point.get_from_edge ()->dyn_cast_cfg_superedge ();
+ if (last_cfg_superedge)
+ next_state.m_region_model->update_for_phis
+ (node->get_supernode (),
+ last_cfg_superedge,
+ &ctxt);
+ }
+
+ if (point.get_supernode ()->m_stmts.length () > 0)
+ {
+ program_point next_point
+ = program_point::before_stmt (point.get_supernode (), 0,
+ point.get_call_string ());
+ exploded_node *next
+ = get_or_create_node (next_point, next_state, &change);
+ if (next)
+ add_edge (node, next, NULL, change);
+ }
+ else
+ {
+ program_point next_point
+ = program_point::after_supernode (point.get_supernode (),
+ point.get_call_string ());
+ exploded_node *next = get_or_create_node (next_point, next_state,
+ &change);
+ if (next)
+ add_edge (node, next, NULL, change);
+ }
+ }
+ break;
+ case PK_BEFORE_STMT:
+ {
+ /* Determine the effect of a run of one or more statements
+ within one supernode, generating an edge to the program_point
+ after the last statement that's processed.
+
+ Stop iterating statements and thus consolidating into one enode
+ when:
+ - reaching the end of the statements in the supernode
+ - if an sm-state-change occurs (so that it gets its own
+ exploded_node)
+ - if "-fanalyzer-fine-grained" is active
+ - encountering certain statements must appear at the start of
+ their enode (for which stmt_requires_new_enode_p returns true)
+
+ Update next_state in-place, to get the result of the one
+ or more stmts that are processed. */
+ program_state next_state (state);
+ state_change change;
+ const supernode *snode = point.get_supernode ();
+ unsigned stmt_idx;
+ const gimple *prev_stmt = NULL;
+ for (stmt_idx = point.get_stmt_idx ();
+ stmt_idx < snode->m_stmts.length ();
+ stmt_idx++)
+ {
+ const gimple *stmt = snode->m_stmts[stmt_idx];
+
+ if (stmt_idx > point.get_stmt_idx ())
+ if (stmt_requires_new_enode_p (stmt, prev_stmt))
+ {
+ stmt_idx--;
+ break;
+ }
+ prev_stmt = stmt;
+
+ /* Process the stmt. */
+ exploded_node::on_stmt_flags flags
+ = node->on_stmt (*this, snode, stmt, &next_state, &change);
+
+ /* If flags.m_terminate_path, stop analyzing; any nodes/edges
+ will have been added by on_stmt (e.g. for handling longjmp). */
+ if (flags.m_terminate_path)
+ return;
+
+ if (flags.m_sm_changes || flag_analyzer_fine_grained)
+ break;
+ }
+ unsigned next_idx = stmt_idx + 1;
+ program_point next_point
+ = (next_idx < point.get_supernode ()->m_stmts.length ()
+ ? program_point::before_stmt (point.get_supernode (), next_idx,
+ point.get_call_string ())
+ : program_point::after_supernode (point.get_supernode (),
+ point.get_call_string ()));
+ exploded_node *next = get_or_create_node (next_point,
+ next_state, &change);
+ if (next)
+ add_edge (node, next, NULL, change);
+ }
+ break;
+ case PK_AFTER_SUPERNODE:
+ {
+ /* If this is an EXIT BB, detect leaks, and potentially
+ create a function summary. */
+ if (point.get_supernode ()->return_p ())
+ {
+ node->detect_leaks (*this);
+ if (flag_analyzer_call_summaries
+ && point.get_call_string ().empty_p ())
+ {
+ /* TODO: create function summary
+ There can be more than one; each corresponds to a different
+ final enode in the function. */
+ if (logger)
+ {
+ pretty_printer *pp = logger->get_printer ();
+ logger->start_log_line ();
+ logger->log_partial
+ ("would create function summary for %qE; state: ",
+ point.get_fndecl ());
+ state.dump_to_pp (m_ext_state, true, pp);
+ logger->end_log_line ();
+ }
+ per_function_data *per_fn_data
+ = get_or_create_per_function_data (point.get_function ());
+ per_fn_data->add_call_summary (node);
+ }
+ }
+ /* Traverse into successors of the supernode. */
+ int i;
+ superedge *succ;
+ FOR_EACH_VEC_ELT (point.get_supernode ()->m_succs, i, succ)
+ {
+ if (logger)
+ logger->log ("considering SN: %i -> SN: %i",
+ succ->m_src->m_index, succ->m_dest->m_index);
+
+ state_change change;
+
+ program_point next_point
+ = program_point::before_supernode (succ->m_dest, succ,
+ point.get_call_string ());
+ program_state next_state (state);
+
+ if (!node->on_edge (*this, succ, &next_point, &next_state, &change))
+ {
+ if (logger)
+ logger->log ("skipping impossible edge to SN: %i",
+ succ->m_dest->m_index);
+ continue;
+ }
+
+ exploded_node *next = get_or_create_node (next_point, next_state,
+ &change);
+ if (next)
+ add_edge (node, next, succ, change);
+ }
+ }
+ break;
+ }
+}
+
+/* Ensure that this graph has a stats instance for FN, return it.
+ FN can be NULL, in which case a stats instances is returned covering
+ "functionless" parts of the graph (the origin node). */
+
+stats *
+exploded_graph::get_or_create_function_stats (function *fn)
+{
+ if (!fn)
+ return &m_functionless_stats;
+
+ if (stats **slot = m_per_function_stats.get (fn))
+ return *slot;
+ else
+ {
+ int num_supernodes = fn ? n_basic_blocks_for_fn (fn) : 0;
+ /* not quite the num supernodes, but nearly. */
+ stats *new_stats = new stats (num_supernodes);
+ m_per_function_stats.put (fn, new_stats);
+ return new_stats;
+ }
+}
+
+/* Write all stats information to this graph's logger, if any. */
+
+void
+exploded_graph::log_stats () const
+{
+ logger * const logger = get_logger ();
+ if (!logger)
+ return;
+
+ LOG_SCOPE (logger);
+
+ logger->log ("m_sg.num_nodes (): %i", m_sg.num_nodes ());
+ logger->log ("m_nodes.length (): %i", m_nodes.length ());
+ logger->log ("m_edges.length (): %i", m_edges.length ());
+
+ logger->log ("global stats:");
+ m_global_stats.log (logger);
+
+ for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+ iter != m_per_function_stats.end ();
+ ++iter)
+ {
+ function *fn = (*iter).first;
+ log_scope s (logger, function_name (fn));
+ (*iter).second->log (logger);
+ }
+}
+
+/* Dump all stats information to OUT. */
+
+void
+exploded_graph::dump_stats (FILE *out) const
+{
+ fprintf (out, "m_sg.num_nodes (): %i\n", m_sg.num_nodes ());
+ fprintf (out, "m_nodes.length (): %i\n", m_nodes.length ());
+ fprintf (out, "m_edges.length (): %i\n", m_edges.length ());
+
+ fprintf (out, "global stats:\n");
+ m_global_stats.dump (out);
+
+ for (function_stat_map_t::iterator iter = m_per_function_stats.begin ();
+ iter != m_per_function_stats.end ();
+ ++iter)
+ {
+ function *fn = (*iter).first;
+ fprintf (out, "function: %s\n", function_name (fn));
+ (*iter).second->dump (out);
+ }
+
+ fprintf (out, "PK_AFTER_SUPERNODE per supernode:\n");
+ for (unsigned i = 0; i < m_PK_AFTER_SUPERNODE_per_snode.length (); i++)
+ fprintf (out, " SN %i: %3i\n", i, m_PK_AFTER_SUPERNODE_per_snode[i]);
+}
+
+void
+exploded_graph::dump_states_for_supernode (FILE *out,
+ const supernode *snode) const
+{
+ fprintf (out, "PK_AFTER_SUPERNODE nodes for SN: %i\n", snode->m_index);
+ int i;
+ exploded_node *enode;
+ int state_idx = 0;
+ FOR_EACH_VEC_ELT (m_nodes, i, enode)
+ {
+ const supernode *iter_snode = enode->get_supernode ();
+ if (enode->get_point ().get_kind () == PK_AFTER_SUPERNODE
+ && iter_snode == snode)
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ enode->get_state ().dump_to_pp (m_ext_state, true, &pp);
+ fprintf (out, "state %i: EN: %i\n %s\n",
+ state_idx++, enode->m_index,
+ pp_formatted_text (&pp));
+ }
+ }
+ fprintf (out, "#exploded_node for PK_AFTER_SUPERNODE for SN: %i = %i\n",
+ snode->m_index, state_idx);
+}
+
+/* Look for the last use of SEARCH_STMT within this path.
+ If found write the edge's index to *OUT_IDX and return true, otherwise
+ return false. */
+
+bool
+exploded_path::find_stmt_backwards (const gimple *search_stmt,
+ int *out_idx) const
+{
+ int i;
+ const exploded_edge *eedge;
+ FOR_EACH_VEC_ELT_REVERSE (m_edges, i, eedge)
+ {
+ const exploded_node *dst_node = eedge->m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const gimple *stmt = dst_point.get_stmt ();
+ if (stmt == search_stmt)
+ {
+ *out_idx = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Get the final exploded_node in this path, which must be non-empty. */
+
+exploded_node *
+exploded_path::get_final_enode () const
+{
+ gcc_assert (m_edges.length () > 0);
+ return m_edges[m_edges.length () - 1]->m_dest;
+}
+
+/* Check state along this path, returning true if it is feasible. */
+
+bool
+exploded_path::feasible_p (logger *logger) const
+{
+ LOG_SCOPE (logger);
+
+ /* Traverse the path, updating this model. */
+ region_model model;
+ for (unsigned i = 0; i < m_edges.length (); i++)
+ {
+ const exploded_edge *eedge = m_edges[i];
+ if (logger)
+ logger->log ("considering edge %i: EN:%i -> EN:%i",
+ i,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ const exploded_node &src_enode = *eedge->m_src;
+ const program_point &src_point = src_enode.get_point ();
+ if (logger)
+ {
+ logger->start_log_line ();
+ src_point.print (logger->get_printer (), format (false));
+ logger->end_log_line ();
+ }
+
+ if (const gimple *stmt = src_point.get_stmt ())
+ {
+ /* Update cfun and input_location in case of ICE: make it easier to
+ track down which source construct we're failing to handle. */
+ auto_cfun sentinel (src_point.get_function ());
+ input_location = stmt->location;
+
+ if (const gassign *assign = dyn_cast <const gassign *> (stmt))
+ model.on_assignment (assign, NULL);
+ else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
+ model.on_return (return_, NULL);
+ }
+
+ const superedge *sedge = eedge->m_sedge;
+ if (sedge)
+ {
+ if (logger)
+ logger->log (" sedge: SN:%i -> SN:%i %s",
+ sedge->m_src->m_index,
+ sedge->m_dest->m_index,
+ sedge->get_description (false));
+
+ const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+ if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL))
+ {
+ if (logger)
+ {
+ logger->log ("rejecting due to region model");
+ model.dump_to_pp (logger->get_printer (), false);
+ }
+ return false;
+ }
+ }
+ else
+ {
+ /* Special-case the initial eedge from the origin node to the
+ initial function by pushing a frame for it. */
+ if (i == 0)
+ {
+ gcc_assert (eedge->m_src->m_index == 0);
+ gcc_assert (src_point.get_kind () == PK_ORIGIN);
+ gcc_assert (eedge->m_dest->get_point ().get_kind ()
+ == PK_BEFORE_SUPERNODE);
+ function *fun = eedge->m_dest->get_function ();
+ gcc_assert (fun);
+ model.push_frame (fun, NULL, NULL);
+ if (logger)
+ logger->log (" pushing frame for %qD", fun->decl);
+ }
+ else if (eedge->m_custom_info)
+ eedge->m_custom_info->update_model (&model, *eedge);
+ }
+
+ /* Handle phi nodes on an edge leaving a PK_BEFORE_SUPERNODE (to
+ a PK_BEFORE_STMT, or a PK_AFTER_SUPERNODE if no stmts).
+ This will typically not be associated with a superedge. */
+ if (src_point.get_from_edge ())
+ {
+ const cfg_superedge *last_cfg_superedge
+ = src_point.get_from_edge ()->dyn_cast_cfg_superedge ();
+ if (last_cfg_superedge)
+ {
+ if (logger)
+ logger->log (" update for phis");
+ model.update_for_phis (src_enode.get_supernode (),
+ last_cfg_superedge,
+ NULL);
+ }
+ }
+
+ if (logger)
+ {
+ logger->log ("state after edge %i: EN:%i -> EN:%i",
+ i,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ logger->start_log_line ();
+ model.dump_to_pp (logger->get_printer (), true);
+ logger->end_log_line ();
+ }
+ }
+
+ return true;
+}
+
+/* Dump this path in multiline form to PP. */
+
+void
+exploded_path::dump_to_pp (pretty_printer *pp) const
+{
+ for (unsigned i = 0; i < m_edges.length (); i++)
+ {
+ const exploded_edge *eedge = m_edges[i];
+ pp_printf (pp, "m_edges[%i]: EN %i -> EN %i",
+ i,
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ pp_newline (pp);
+ }
+}
+
+/* Dump this path in multiline form to FP. */
+
+void
+exploded_path::dump (FILE *fp) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = fp;
+ dump_to_pp (&pp);
+ pp_flush (&pp);
+}
+
+/* Dump this path in multiline form to stderr. */
+
+DEBUG_FUNCTION void
+exploded_path::dump () const
+{
+ dump (stderr);
+}
+
+/* A family of cluster subclasses for use when generating .dot output for
+ exploded graphs (-fdump-analyzer-exploded-graph), for grouping the
+ enodes into hierarchical boxes.
+
+ All functionless enodes appear in the top-level graph.
+ Every (function, call_string) pair gets its own cluster. Within that
+ cluster, each supernode gets its own cluster.
+
+ Hence all enodes relating to a particular function with a particular
+ callstring will be be in a cluster together; all enodes for the same
+ function but with a different callstring will be in a different
+ cluster. */
+
+/* Base class of cluster for clustering exploded_node instances in .dot
+ output, based on various subclass-specific criteria. */
+
+class exploded_cluster : public cluster<eg_traits>
+{
+};
+
+/* Cluster containing all exploded_node instances for one supernode. */
+
+class supernode_cluster : public exploded_cluster
+{
+public:
+ supernode_cluster (const supernode *supernode) : m_supernode (supernode) {}
+
+ // TODO: dtor?
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+ {
+ gv->println ("subgraph \"cluster_supernode_%p\" {",
+ (const void *)this);
+ gv->indent ();
+ gv->println ("style=\"dashed\";");
+ gv->println ("label=\"SN: %i\";", m_supernode->m_index);
+
+ int i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (m_enodes, i, enode)
+ enode->dump_dot (gv, args);
+
+ /* Terminate subgraph. */
+ gv->outdent ();
+ gv->println ("}");
+ }
+
+ void add_node (exploded_node *en) FINAL OVERRIDE
+ {
+ m_enodes.safe_push (en);
+ }
+
+private:
+ const supernode *m_supernode;
+ auto_vec <exploded_node *> m_enodes;
+};
+
+/* Cluster containing all supernode_cluster instances for one
+ (function, call_string) pair. */
+
+class function_call_string_cluster : public exploded_cluster
+{
+public:
+ function_call_string_cluster (function *fun, call_string cs)
+ : m_fun (fun), m_cs (cs) {}
+
+ ~function_call_string_cluster ()
+ {
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ delete (*iter).second;
+ }
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+ {
+ const char *funcname = function_name (m_fun);
+
+ gv->println ("subgraph \"cluster_function_%p\" {", (const void *)this);
+ gv->indent ();
+ gv->write_indent ();
+ gv->print ("label=\"call string: ");
+ m_cs.print (gv->get_pp ());
+ gv->print (" function: %s \";", funcname);
+ gv->print ("\n");
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ (*iter).second->dump_dot (gv, args);
+
+ /* Terminate subgraph. */
+ gv->outdent ();
+ gv->println ("}");
+ }
+
+ void add_node (exploded_node *en) FINAL OVERRIDE
+ {
+ const supernode *supernode = en->get_supernode ();
+ gcc_assert (supernode);
+ supernode_cluster **slot = m_map.get (supernode);
+ if (slot)
+ (*slot)->add_node (en);
+ else
+ {
+ supernode_cluster *child = new supernode_cluster (supernode);
+ m_map.put (supernode, child);
+ child->add_node (en);
+ }
+ }
+
+private:
+ function *m_fun;
+ call_string m_cs;
+ typedef ordered_hash_map<const supernode *, supernode_cluster *> map_t;
+ map_t m_map;
+};
+
+/* Keys for root_cluster. */
+
+struct function_call_string
+{
+ function_call_string (function *fun, call_string cs)
+ : m_fun (fun), m_cs (cs)
+ {
+ gcc_assert (fun);
+ }
+
+ function *m_fun;
+ call_string m_cs;
+};
+
+template <> struct default_hash_traits<function_call_string>
+: public pod_hash_traits<function_call_string>
+{
+ static const bool empty_zero_p = false;
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<function_call_string>::hash (value_type v)
+{
+ return pointer_hash <function>::hash (v.m_fun) ^ v.m_cs.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<function_call_string>::equal (const value_type &existing,
+ const value_type &candidate)
+{
+ return existing.m_fun == candidate.m_fun && existing.m_cs == candidate.m_cs;
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_deleted (value_type &v)
+{
+ v.m_fun = reinterpret_cast<function *> (1);
+}
+template <>
+inline void
+pod_hash_traits<function_call_string>::mark_empty (value_type &v)
+{
+ v.m_fun = reinterpret_cast<function *> (NULL);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_deleted (value_type v)
+{
+ return v.m_fun == reinterpret_cast<function *> (1);
+}
+template <>
+inline bool
+pod_hash_traits<function_call_string>::is_empty (value_type v)
+{
+ return v.m_fun == reinterpret_cast<function *> (NULL);
+}
+
+/* Top-level cluster for generating .dot output for exploded graphs,
+ handling the functionless nodes, and grouping the remaining nodes by
+ callstring. */
+
+class root_cluster : public exploded_cluster
+{
+public:
+ ~root_cluster ()
+ {
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ delete (*iter).second;
+ }
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+ {
+ int i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (m_functionless_enodes, i, enode)
+ enode->dump_dot (gv, args);
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ (*iter).second->dump_dot (gv, args);
+ }
+
+ void add_node (exploded_node *en) FINAL OVERRIDE
+ {
+ function *fun = en->get_function ();
+ if (!fun)
+ {
+ m_functionless_enodes.safe_push (en);
+ return;
+ }
+
+ const call_string &cs = en->get_point ().get_call_string ();
+ function_call_string key (fun, cs);
+ function_call_string_cluster **slot = m_map.get (key);
+ if (slot)
+ (*slot)->add_node (en);
+ else
+ {
+ function_call_string_cluster *child
+ = new function_call_string_cluster (fun, cs);
+ m_map.put (key, child);
+ child->add_node (en);
+ }
+ }
+
+private:
+ /* This can't be an ordered_hash_map, as we can't store vec<call_string>,
+ since it's not a POD; vec<>::quick_push has:
+ *slot = obj;
+ and the slot isn't initialized, so the assignment op dies when cleaning up
+ un-inited *slot (within the truncate call). */
+ typedef hash_map<function_call_string, function_call_string_cluster *> map_t;
+ map_t m_map;
+
+ /* This should just be the origin exploded_node. */
+ auto_vec <exploded_node *> m_functionless_enodes;
+};
+
+/* Subclass of range_label for use within
+ exploded_graph::dump_exploded_nodes for implementing
+ -fdump-analyzer-exploded-nodes: a label for a specific
+ exploded_node. */
+
+class enode_label : public range_label
+{
+ public:
+ enode_label (const extrinsic_state &ext_state,
+ exploded_node *enode)
+ : m_ext_state (ext_state), m_enode (enode) {}
+
+ label_text get_text (unsigned) const FINAL OVERRIDE
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ m_enode->get_state ().dump_to_pp (m_ext_state, true, &pp);
+ return make_label_text (false, "EN: %i: %s",
+ m_enode->m_index, pp_formatted_text (&pp));
+ }
+
+private:
+ const extrinsic_state &m_ext_state;
+ exploded_node *m_enode;
+};
+
+/* Postprocessing support for dumping the exploded nodes.
+ Handle -fdump-analyzer-exploded-nodes,
+ -fdump-analyzer-exploded-nodes-2, and the
+ "__analyzer_dump_exploded_nodes" builtin. */
+
+void
+exploded_graph::dump_exploded_nodes () const
+{
+ // TODO
+ /* Locate calls to __analyzer_dump_exploded_nodes. */
+ // Print how many egs there are for them?
+ /* Better: log them as we go, and record the exploded nodes
+ in question. */
+
+ /* Show every enode. */
+
+ /* Gather them by stmt, so that we can more clearly see the
+ "hotspots" requiring numerous exploded nodes. */
+
+ /* Alternatively, simply throw them all into one big rich_location
+ and see if the label-printing will sort it out...
+ This requires them all to be in the same source file. */
+
+ if (flag_dump_analyzer_exploded_nodes)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ gcc_rich_location richloc (UNKNOWN_LOCATION);
+ unsigned i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (m_nodes, i, enode)
+ {
+ if (const gimple *stmt = enode->get_stmt ())
+ {
+ if (richloc.get_loc () == UNKNOWN_LOCATION)
+ richloc.set_range (0, stmt->location, SHOW_RANGE_WITH_CARET);
+ else
+ richloc.add_range (stmt->location,
+ SHOW_RANGE_WITHOUT_CARET,
+ new enode_label (m_ext_state, enode));
+ }
+ }
+ warning_at (&richloc, 0, "%i exploded nodes", m_nodes.length ());
+
+ /* Repeat the warning without all the labels, so that message is visible
+ (the other one may well have scrolled past the terminal limit). */
+ warning_at (richloc.get_loc (), 0,
+ "%i exploded nodes", m_nodes.length ());
+
+ if (m_worklist.length () > 0)
+ warning_at (richloc.get_loc (), 0,
+ "worklist still contains %i nodes", m_worklist.length ());
+ }
+
+ /* Dump the egraph in textual form to a dump file. */
+ if (flag_dump_analyzer_exploded_nodes_2)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ char *filename
+ = concat (dump_base_name, ".eg.txt", NULL);
+ FILE *outf = fopen (filename, "w");
+ if (!outf)
+ error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", filename);
+ free (filename);
+
+ fprintf (outf, "exploded graph for %s\n", dump_base_name);
+ fprintf (outf, " nodes: %i\n", m_nodes.length ());
+ fprintf (outf, " edges: %i\n", m_edges.length ());
+
+ unsigned i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (m_nodes, i, enode)
+ {
+ fprintf (outf, "\nEN %i:\n", enode->m_index);
+ enode->dump_succs_and_preds (outf);
+ pretty_printer pp;
+ enode->get_point ().print (&pp, format (true));
+ fprintf (outf, "%s\n", pp_formatted_text (&pp));
+ enode->get_state ().dump_to_file (m_ext_state, false, outf);
+ }
+
+ fclose (outf);
+ }
+
+ /* Dump the egraph in textual form to multiple dump files, one per enode. */
+ if (flag_dump_analyzer_exploded_nodes_3)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+
+ unsigned i;
+ exploded_node *enode;
+ FOR_EACH_VEC_ELT (m_nodes, i, enode)
+ {
+ char *filename
+ = xasprintf ("%s.en-%i.txt", dump_base_name, i);
+ FILE *outf = fopen (filename, "w");
+ if (!outf)
+ error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", filename);
+ free (filename);
+
+ fprintf (outf, "EN %i:\n", enode->m_index);
+ enode->dump_succs_and_preds (outf);
+ pretty_printer pp;
+ enode->get_point ().print (&pp, format (true));
+ fprintf (outf, "%s\n", pp_formatted_text (&pp));
+ enode->get_state ().dump_to_file (m_ext_state, false, outf);
+
+ fclose (outf);
+ }
+ }
+
+ /* Emit a warning at any call to "__analyzer_dump_exploded_nodes",
+ giving the number of exploded nodes for "before-stmt", and their
+ IDs. */
+
+ unsigned i;
+ exploded_node *enode;
+ hash_set<const gimple *> seen;
+ FOR_EACH_VEC_ELT (m_nodes, i, enode)
+ {
+ if (enode->get_point ().get_kind () != PK_BEFORE_STMT)
+ continue;
+
+ if (const gimple *stmt = enode->get_stmt ())
+ if (const gcall *call = dyn_cast <const gcall *> (stmt))
+ if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
+ 1))
+ {
+ if (seen.contains (stmt))
+ continue;
+
+ /* This is O(N^2). */
+ unsigned j;
+ auto_vec<exploded_node *> enodes;
+ exploded_node *other_enode;
+ FOR_EACH_VEC_ELT (m_nodes, j, other_enode)
+ {
+ if (other_enode->get_point ().get_kind () != PK_BEFORE_STMT)
+ continue;
+ if (other_enode->get_stmt () == stmt)
+ enodes.safe_push (other_enode);
+ }
+
+ pretty_printer pp;
+ print_enode_indices (&pp, enodes);
+
+ warning_n (stmt->location, 0, enodes.length (),
+ "%i exploded node: %s",
+ "%i exploded nodes: %s",
+ enodes.length (), pp_formatted_text (&pp));
+ seen.add (stmt);
+
+ /* If the argument is non-zero, then print all of the states
+ of the various enodes. */
+ tree t_arg = fold (gimple_call_arg (call, 0));
+ if (TREE_CODE (t_arg) != INTEGER_CST)
+ {
+ error_at (call->location,
+ "integer constant required for arg 1");
+ return;
+ }
+ int i_arg = TREE_INT_CST_LOW (t_arg);
+ if (i_arg)
+ {
+ exploded_node *other_enode;
+ FOR_EACH_VEC_ELT (enodes, j, other_enode)
+ {
+ fprintf (stderr, "%i of %i: EN %i:\n",
+ j + 1, enodes.length (), other_enode->m_index);
+ other_enode->dump_succs_and_preds (stderr);
+ /* Dump state. */
+ other_enode->get_state ().dump (m_ext_state, false);
+ }
+ }
+ }
+ }
+}
+
+/* A collection of classes for visualizing the callgraph in .dot form
+ (as represented in the supergraph). */
+
+/* Forward decls. */
+class viz_callgraph_node;
+class viz_callgraph_edge;
+class viz_callgraph;
+class viz_callgraph_cluster;
+
+/* Traits for using "digraph.h" to visualize the callgraph. */
+
+struct viz_callgraph_traits
+{
+ typedef viz_callgraph_node node_t;
+ typedef viz_callgraph_edge edge_t;
+ typedef viz_callgraph graph_t;
+ struct dump_args_t
+ {
+ dump_args_t (const exploded_graph *eg) : m_eg (eg) {}
+ const exploded_graph *m_eg;
+ };
+ typedef viz_callgraph_cluster cluster_t;
+};
+
+/* Subclass of dnode representing a function within the callgraph. */
+
+class viz_callgraph_node : public dnode<viz_callgraph_traits>
+{
+ friend class viz_callgraph;
+
+public:
+ viz_callgraph_node (function *fun, int index)
+ : m_fun (fun), m_index (index), m_num_supernodes (0), m_num_superedges (0)
+ {
+ gcc_assert (fun);
+ }
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE
+ {
+ pretty_printer *pp = gv->get_pp ();
+
+ dump_dot_id (pp);
+ pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=<",
+ "lightgrey");
+ pp_string (pp, "<TABLE BORDER=\"0\">");
+ pp_write_text_to_stream (pp);
+
+ gv->begin_tr ();
+ pp_printf (pp, "VCG: %i: %s", m_index, function_name (m_fun));
+ gv->end_tr ();
+ pp_newline (pp);
+
+ gv->begin_tr ();
+ pp_printf (pp, "supernodes: %i\n", m_num_supernodes);
+ gv->end_tr ();
+ pp_newline (pp);
+
+ gv->begin_tr ();
+ pp_printf (pp, "superedges: %i\n", m_num_superedges);
+ gv->end_tr ();
+ pp_newline (pp);
+
+ if (args.m_eg)
+ {
+ unsigned i;
+ exploded_node *enode;
+ unsigned num_enodes = 0;
+ FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
+ {
+ if (enode->get_point ().get_function () == m_fun)
+ num_enodes++;
+ }
+ gv->begin_tr ();
+ pp_printf (pp, "enodes: %i\n", num_enodes);
+ gv->end_tr ();
+ pp_newline (pp);
+
+ // TODO: also show the per-callstring breakdown
+ const exploded_graph::call_string_data_map_t *per_cs_data
+ = args.m_eg->get_per_call_string_data ();
+ for (typename exploded_graph::call_string_data_map_t::iterator iter
+ = per_cs_data->begin ();
+ iter != per_cs_data->end ();
+ ++iter)
+ {
+ const call_string *cs = (*iter).first;
+ //per_call_string_data *data = (*iter).second;
+ num_enodes = 0;
+ FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
+ {
+ if (enode->get_point ().get_function () == m_fun
+ && enode->get_point ().get_call_string () == *cs)
+ num_enodes++;
+ }
+ if (num_enodes > 0)
+ {
+ gv->begin_tr ();
+ cs->print (pp);
+ pp_printf (pp, ": %i\n", num_enodes);
+ pp_write_text_as_html_like_dot_to_stream (pp);
+ gv->end_tr ();
+ }
+ }
+
+ /* Show any summaries. */
+ per_function_data *data = args.m_eg->get_per_function_data (m_fun);
+ if (data)
+ {
+ pp_newline (pp);
+ gv->begin_tr ();
+ pp_printf (pp, "summaries: %i\n", data->m_summaries.length ());
+ pp_write_text_as_html_like_dot_to_stream (pp);
+ gv->end_tr ();
+ }
+ }
+
+ pp_string (pp, "</TABLE>>];\n\n");
+ pp_flush (pp);
+ }
+
+ void dump_dot_id (pretty_printer *pp) const
+ {
+ pp_printf (pp, "vcg_%i", m_index);
+ }
+
+private:
+ function *m_fun;
+ int m_index;
+ int m_num_supernodes;
+ int m_num_superedges;
+};
+
+/* Subclass of dedge representing a callgraph edge. */
+
+class viz_callgraph_edge : public dedge<viz_callgraph_traits>
+{
+public:
+ viz_callgraph_edge (viz_callgraph_node *src, viz_callgraph_node *dest,
+ const call_superedge *call_sedge)
+ : dedge (src, dest),
+ m_call_sedge (call_sedge)
+ {}
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &) const
+ FINAL OVERRIDE
+ {
+ pretty_printer *pp = gv->get_pp ();
+
+ const char *style = "\"solid,bold\"";
+ const char *color = "black";
+ int weight = 10;
+ const char *constraint = "true";
+
+ m_src->dump_dot_id (pp);
+ pp_string (pp, " -> ");
+ m_dest->dump_dot_id (pp);
+ pp_printf (pp,
+ (" [style=%s, color=%s, weight=%d, constraint=%s,"
+ " headlabel=\""),
+ style, color, weight, constraint);
+ pp_printf (pp, "\"];\n");
+ }
+
+private:
+ const call_superedge * const m_call_sedge;
+};
+
+/* Subclass of digraph representing the callgraph. */
+
+class viz_callgraph : public digraph<viz_callgraph_traits>
+{
+public:
+ viz_callgraph (const supergraph &sg);
+
+ viz_callgraph_node *get_vcg_node_for_function (function *fun)
+ {
+ return *m_map.get (fun);
+ }
+
+ viz_callgraph_node *get_vcg_node_for_snode (supernode *snode)
+ {
+ return get_vcg_node_for_function (snode->m_fun);
+ }
+
+private:
+ const supergraph &m_sg;
+ hash_map<function *, viz_callgraph_node *> m_map;
+};
+
+/* Placeholder subclass of cluster. */
+
+class viz_callgraph_cluster : public cluster<viz_callgraph_traits>
+{
+};
+
+/* viz_callgraph's ctor. */
+
+viz_callgraph::viz_callgraph (const supergraph &sg)
+: m_sg (sg)
+{
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ {
+ function *fun = node->get_fun ();
+ viz_callgraph_node *vcg_node
+ = new viz_callgraph_node (fun, m_nodes.length ());
+ m_map.put (fun, vcg_node);
+ add_node (vcg_node);
+ }
+
+ unsigned i;
+ superedge *sedge;
+ FOR_EACH_VEC_ELT (sg.m_edges, i, sedge)
+ {
+ viz_callgraph_node *vcg_src = get_vcg_node_for_snode (sedge->m_src);
+ if (vcg_src->m_fun)
+ get_vcg_node_for_function (vcg_src->m_fun)->m_num_superedges++;
+ if (const call_superedge *call_sedge = sedge->dyn_cast_call_superedge ())
+ {
+ viz_callgraph_node *vcg_dest = get_vcg_node_for_snode (sedge->m_dest);
+ viz_callgraph_edge *vcg_edge
+ = new viz_callgraph_edge (vcg_src, vcg_dest, call_sedge);
+ add_edge (vcg_edge);
+ }
+ }
+
+ supernode *snode;
+ FOR_EACH_VEC_ELT (sg.m_nodes, i, snode)
+ {
+ if (snode->m_fun)
+ get_vcg_node_for_function (snode->m_fun)->m_num_supernodes++;
+ }
+}
+
+/* Dump the callgraph to FILENAME. */
+
+static void
+dump_callgraph (const supergraph &sg, const char *filename,
+ const exploded_graph *eg)
+{
+ FILE *outf = fopen (filename, "w");
+ if (!outf)
+ return;
+
+ // TODO
+ viz_callgraph vcg (sg);
+ vcg.dump_dot (filename, NULL, viz_callgraph_traits::dump_args_t (eg));
+
+ fclose (outf);
+}
+
+/* Dump the callgraph to "<srcfile>.callgraph.dot". */
+
+static void
+dump_callgraph (const supergraph &sg, const exploded_graph *eg)
+{
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ char *filename = concat (dump_base_name, ".callgraph.dot", NULL);
+ dump_callgraph (sg, filename, eg);
+ free (filename);
+}
+
+/* Run the analysis "engine". */
+
+void
+impl_run_checkers (logger *logger)
+{
+ LOG_SCOPE (logger);
+
+ /* If using LTO, ensure that the cgraph nodes have function bodies. */
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ node->get_untransformed_body ();
+
+ /* Create the supergraph. */
+ supergraph sg (logger);
+
+ state_purge_map *purge_map = NULL;
+
+ if (flag_analyzer_state_purge)
+ purge_map = new state_purge_map (sg, logger);
+
+ if (flag_dump_analyzer_supergraph)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
+ supergraph::dump_args_t args ((enum supergraph_dot_flags)0, NULL);
+ sg.dump_dot (filename, args);
+ free (filename);
+ }
+
+ if (flag_dump_analyzer_state_purge)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ state_purge_annotator a (purge_map);
+ char *filename = concat (dump_base_name, ".state-purge.dot", NULL);
+ supergraph::dump_args_t args ((enum supergraph_dot_flags)0, &a);
+ sg.dump_dot (filename, args);
+ free (filename);
+ }
+
+ auto_delete_vec <state_machine> checkers;
+ make_checkers (checkers, logger);
+
+ if (logger)
+ {
+ int i;
+ state_machine *sm;
+ FOR_EACH_VEC_ELT (checkers, i, sm)
+ logger->log ("checkers[%i]: %s", i, sm->get_name ());
+ }
+
+ /* Extrinsic state shared by nodes in the graph. */
+ const extrinsic_state ext_state (checkers);
+
+ const analysis_plan plan (sg, logger);
+
+ /* The exploded graph. */
+ exploded_graph eg (sg, logger, ext_state, purge_map, plan,
+ analyzer_verbosity);
+
+ /* Add entrypoints to the graph for externally-callable functions. */
+ eg.build_initial_worklist ();
+
+ /* Now process the worklist, exploring the <point, state> graph. */
+ eg.process_worklist ();
+
+ if (flag_dump_analyzer_exploded_graph)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ char *filename
+ = concat (dump_base_name, ".eg.dot", NULL);
+ exploded_graph::dump_args_t args (eg);
+ root_cluster c;
+ eg.dump_dot (filename, &c, args);
+ free (filename);
+ }
+
+ /* Now emit any saved diagnostics. */
+ eg.get_diagnostic_manager ().emit_saved_diagnostics (eg);
+
+ eg.dump_exploded_nodes ();
+
+ eg.log_stats ();
+
+ if (flag_dump_analyzer_callgraph)
+ dump_callgraph (sg, &eg);
+
+ delete purge_map;
+}
+
+/* External entrypoint to the analysis "engine".
+ Set up any dumps, then call impl_run_checkers. */
+
+void
+run_checkers ()
+{
+ /* Handle -fdump-analyzer and -fdump-analyzer-stderr. */
+ FILE *dump_fout = NULL;
+ /* Track if we're responsible for closing dump_fout. */
+ bool owns_dump_fout = false;
+ if (flag_dump_analyzer_stderr)
+ dump_fout = stderr;
+ else if (flag_dump_analyzer)
+ {
+ char *dump_filename = concat (dump_base_name, ".analyzer.txt", NULL);
+ dump_fout = fopen (dump_filename, "w");
+ free (dump_filename);
+ if (dump_fout)
+ owns_dump_fout = true;
+ }
+
+ {
+ log_user the_logger (NULL);
+ if (dump_fout)
+ the_logger.set_logger (new logger (dump_fout, 0, 0,
+ *global_dc->printer));
+ LOG_SCOPE (the_logger.get_logger ());
+
+ impl_run_checkers (the_logger.get_logger ());
+
+ /* end of lifetime of the_logger (so that dump file is closed after the
+ various dtors run). */
+ }
+
+ if (owns_dump_fout)
+ fclose (dump_fout);
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/engine.h b/gcc/analyzer/engine.h
new file mode 100644
index 00000000000..bbad2e2fe04
--- /dev/null
+++ b/gcc/analyzer/engine.h
@@ -0,0 +1,26 @@
+/* The analysis "engine".
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_ENGINE_H
+#define GCC_ANALYZER_ENGINE_H
+
+extern void run_checkers ();
+
+#endif /* GCC_ANALYZER_ENGINE_H */
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
new file mode 100644
index 00000000000..8c29e552cac
--- /dev/null
+++ b/gcc/analyzer/exploded-graph.h
@@ -0,0 +1,829 @@
+/* Classes for managing a directed graph of <point, state> pairs.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_EXPLODED_GRAPH_H
+#define GCC_ANALYZER_EXPLODED_GRAPH_H
+
+/* Concrete implementation of region_model_context, wiring it up to the
+ rest of the analysis engine. */
+
+class impl_region_model_context : public region_model_context
+{
+ public:
+ impl_region_model_context (exploded_graph &eg,
+ const exploded_node *enode_for_diag,
+
+ /* TODO: should we be getting the ECs from the
+ old state, rather than the new? */
+ const program_state *old_state,
+ program_state *new_state,
+ state_change *change,
+
+ const gimple *stmt,
+ stmt_finder *stmt_finder = NULL);
+
+ impl_region_model_context (program_state *state,
+ state_change *change,
+ const extrinsic_state &ext_state);
+
+ void warn (pending_diagnostic *d) FINAL OVERRIDE;
+
+ void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE;
+
+ int on_svalue_purge (svalue_id first_unused_sid,
+ const svalue_id_map &map) FINAL OVERRIDE;
+
+ logger *get_logger () FINAL OVERRIDE
+ {
+ return m_logger.get_logger ();
+ }
+
+ void on_state_leak (const state_machine &sm,
+ int sm_idx,
+ svalue_id sid,
+ svalue_id first_unused_sid,
+ const svalue_id_map &map,
+ state_machine::state_t state);
+
+ void on_inherited_svalue (svalue_id parent_sid,
+ svalue_id child_sid) FINAL OVERRIDE;
+
+ void on_cast (svalue_id src_sid,
+ svalue_id dst_sid) FINAL OVERRIDE;
+
+ void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
+
+ exploded_graph *m_eg;
+ log_user m_logger;
+ const exploded_node *m_enode_for_diag;
+ const program_state *m_old_state;
+ program_state *m_new_state;
+ state_change *m_change;
+ const gimple *m_stmt;
+ stmt_finder *m_stmt_finder;
+ const extrinsic_state &m_ext_state;
+};
+
+/* A <program_point, program_state> pair, used internally by
+ exploded_node as its immutable data, and as a key for identifying
+ exploded_nodes we've already seen in the graph. */
+
+class point_and_state
+{
+public:
+ point_and_state (const program_point &point,
+ const program_state &state)
+ : m_point (point),
+ m_state (state),
+ m_hash (m_point.hash () ^ m_state.hash ())
+ {
+ }
+
+ hashval_t hash () const
+ {
+ return m_hash;
+ }
+ bool operator== (const point_and_state &other) const
+ {
+ return m_point == other.m_point && m_state == other.m_state;
+ }
+
+ const program_point &get_point () const { return m_point; }
+ const program_state &get_state () const { return m_state; }
+
+ void set_state (const program_state &state)
+ {
+ m_state = state;
+ m_hash = m_point.hash () ^ m_state.hash ();
+ }
+
+ void validate (const extrinsic_state &ext_state) const;
+
+private:
+ program_point m_point;
+ program_state m_state;
+ hashval_t m_hash;
+};
+
+/* A traits class for exploded graphs and their nodes and edges. */
+
+struct eg_traits
+{
+ typedef exploded_node node_t;
+ typedef exploded_edge edge_t;
+ typedef exploded_graph graph_t;
+ struct dump_args_t
+ {
+ dump_args_t (const exploded_graph &eg) : m_eg (eg) {}
+ const exploded_graph &m_eg;
+ };
+ typedef exploded_cluster cluster_t;
+};
+
+/* An exploded_node is a unique, immutable <point, state> pair within the
+ exploded_graph.
+ Each exploded_node has a unique index within the graph
+ (for ease of debugging). */
+
+class exploded_node : public dnode<eg_traits>
+{
+ public:
+ exploded_node (point_and_state ps,
+ int index)
+ : m_ps (ps), m_index (index)
+ {
+ gcc_checking_assert (ps.get_state ().m_region_model->canonicalized_p ());
+ }
+
+ hashval_t hash () const { return m_ps.hash (); }
+
+ void dump_dot (graphviz_out *gv, const dump_args_t &args)
+ const FINAL OVERRIDE;
+ void dump_dot_id (pretty_printer *pp) const;
+
+ void dump_to_pp (pretty_printer *pp, const extrinsic_state &ext_state) const;
+ void dump (FILE *fp, const extrinsic_state &ext_state) const;
+ void dump (const extrinsic_state &ext_state) const;
+
+ /* The result of on_stmt. */
+ struct on_stmt_flags
+ {
+ on_stmt_flags (bool sm_changes)
+ : m_sm_changes (sm_changes),
+ m_terminate_path (false)
+ {}
+
+ static on_stmt_flags terminate_path ()
+ {
+ return on_stmt_flags (true, true);
+ }
+
+ static on_stmt_flags state_change (bool any_sm_changes)
+ {
+ return on_stmt_flags (any_sm_changes, false);
+ }
+
+ /* Did any sm-changes occur handling the stmt. */
+ bool m_sm_changes : 1;
+
+ /* Should we stop analyzing this path (on_stmt may have already
+ added nodes/edges, e.g. when handling longjmp). */
+ bool m_terminate_path : 1;
+
+ private:
+ on_stmt_flags (bool sm_changes,
+ bool terminate_path)
+ : m_sm_changes (sm_changes),
+ m_terminate_path (terminate_path)
+ {}
+ };
+
+ on_stmt_flags on_stmt (exploded_graph &eg,
+ const supernode *snode,
+ const gimple *stmt,
+ program_state *state,
+ state_change *change) const;
+ bool on_edge (exploded_graph &eg,
+ const superedge *succ,
+ program_point *next_point,
+ program_state *next_state,
+ state_change *change) const;
+ void on_longjmp (exploded_graph &eg,
+ const gcall *call,
+ program_state *new_state,
+ region_model_context *ctxt) const;
+
+ void detect_leaks (exploded_graph &eg) const;
+
+ const program_point &get_point () const { return m_ps.get_point (); }
+ const supernode *get_supernode () const
+ {
+ return get_point ().get_supernode ();
+ }
+ function *get_function () const
+ {
+ return get_point ().get_function ();
+ }
+ int get_stack_depth () const
+ {
+ return get_point ().get_stack_depth ();
+ }
+ const gimple *get_stmt () const { return get_point ().get_stmt (); }
+
+ const program_state &get_state () const { return m_ps.get_state (); }
+
+ const point_and_state *get_ps_key () const { return &m_ps; }
+ const program_point *get_point_key () const { return &m_ps.get_point (); }
+
+ void dump_succs_and_preds (FILE *outf) const;
+
+private:
+ DISABLE_COPY_AND_ASSIGN (exploded_node);
+
+ const char * get_dot_fillcolor () const;
+
+ /* The <program_point, program_state> pair. This is const, as it
+ is immutable once the exploded_node has been created. */
+ const point_and_state m_ps;
+
+public:
+ /* The index of this exploded_node. */
+ const int m_index;
+};
+
+/* An edge within the exploded graph.
+ Some exploded_edges have an underlying superedge; others don't. */
+
+class exploded_edge : public dedge<eg_traits>
+{
+ public:
+ /* Abstract base class for associating custom data with an
+ exploded_edge, for handling non-standard edges such as
+ rewinding from a longjmp, signal handlers, etc. */
+ class custom_info_t
+ {
+ public:
+ virtual ~custom_info_t () {}
+
+ /* Hook for making .dot label more readable . */
+ virtual void print (pretty_printer *pp) = 0;
+
+ /* Hook for updating MODEL within exploded_path::feasible_p. */
+ virtual void update_model (region_model *model,
+ const exploded_edge &eedge) = 0;
+
+ virtual void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) = 0;
+ };
+
+ exploded_edge (exploded_node *src, exploded_node *dest,
+ const superedge *sedge,
+ const state_change &change,
+ custom_info_t *custom_info);
+ ~exploded_edge ();
+ void dump_dot (graphviz_out *gv, const dump_args_t &args)
+ const FINAL OVERRIDE;
+
+ //private:
+ const superedge *const m_sedge;
+
+ const state_change m_change;
+
+ /* NULL for most edges; will be non-NULL for special cases
+ such as an unwind from a longjmp to a setjmp, or when
+ a signal is delivered to a signal-handler.
+
+ Owned by this class. */
+ custom_info_t *m_custom_info;
+
+private:
+ DISABLE_COPY_AND_ASSIGN (exploded_edge);
+};
+
+/* Extra data for an exploded_edge that represents a rewind from a
+ longjmp to a setjmp. */
+
+class rewind_info_t : public exploded_edge::custom_info_t
+{
+public:
+ rewind_info_t (const exploded_node *enode_origin)
+ : m_enode_origin (enode_origin)
+ {}
+
+ void print (pretty_printer *pp) FINAL OVERRIDE
+ {
+ pp_string (pp, "rewind");
+ }
+
+ void update_model (region_model *model,
+ const exploded_edge &eedge) FINAL OVERRIDE;
+
+ void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) FINAL OVERRIDE;
+
+ const program_point &get_setjmp_point () const
+ {
+ const program_point &origin_point = m_enode_origin->get_point ();
+
+ /* "origin_point" ought to be before the call to "setjmp". */
+ gcc_assert (origin_point.get_kind () == PK_BEFORE_STMT);
+
+ /* TODO: assert that it's the final stmt in its supernode. */
+
+ return origin_point;
+ }
+
+ const gcall *get_setjmp_call () const
+ {
+ return as_a <const gcall *> (get_setjmp_point ().get_stmt ());
+ }
+
+ const exploded_node *get_enode_origin () const { return m_enode_origin; }
+
+private:
+ const exploded_node *m_enode_origin;
+};
+
+/* Statistics about aspects of an exploded_graph. */
+
+struct stats
+{
+ stats (int num_supernodes);
+ void log (logger *logger) const;
+ void dump (FILE *out) const;
+
+ int m_num_nodes[NUM_POINT_KINDS];
+ int m_node_reuse_count;
+ int m_node_reuse_after_merge_count;
+ int m_num_supernodes;
+};
+
+/* Traits class for ensuring uniqueness of point_and_state data within
+ an exploded_graph. */
+
+struct eg_hash_map_traits
+{
+ typedef const point_and_state *key_type;
+ typedef exploded_node *value_type;
+ typedef exploded_node *compare_type;
+
+ static inline hashval_t hash (const key_type &k)
+ {
+ gcc_assert (k != NULL);
+ gcc_assert (k != reinterpret_cast<key_type> (1));
+ return k->hash ();
+ }
+ static inline bool equal_keys (const key_type &k1, const key_type &k2)
+ {
+ gcc_assert (k1 != NULL);
+ gcc_assert (k2 != NULL);
+ gcc_assert (k1 != reinterpret_cast<key_type> (1));
+ gcc_assert (k2 != reinterpret_cast<key_type> (1));
+ if (k1 && k2)
+ return *k1 == *k2;
+ else
+ /* Otherwise they must both be non-NULL. */
+ return k1 == k2;
+ }
+ template <typename T>
+ static inline void remove (T &)
+ {
+ /* empty; the nodes are handled elsewhere. */
+ }
+ template <typename T>
+ static inline void mark_deleted (T &entry)
+ {
+ entry.m_key = reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline void mark_empty (T &entry)
+ {
+ entry.m_key = NULL;
+ }
+ template <typename T>
+ static inline bool is_deleted (const T &entry)
+ {
+ return entry.m_key == reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline bool is_empty (const T &entry)
+ {
+ return entry.m_key == NULL;
+ }
+ static const bool empty_zero_p = false;
+};
+
+/* Per-program_point data for an exploded_graph. */
+
+struct per_program_point_data
+{
+ per_program_point_data (const program_point &key)
+ : m_key (key)
+ {}
+
+ const program_point m_key;
+ auto_vec<exploded_node *> m_enodes;
+};
+
+/* Traits class for storing per-program_point data within
+ an exploded_graph. */
+
+struct eg_point_hash_map_traits
+{
+ typedef const program_point *key_type;
+ typedef per_program_point_data *value_type;
+ typedef per_program_point_data *compare_type;
+
+ static inline hashval_t hash (const key_type &k)
+ {
+ gcc_assert (k != NULL);
+ gcc_assert (k != reinterpret_cast<key_type> (1));
+ return k->hash ();
+ }
+ static inline bool equal_keys (const key_type &k1, const key_type &k2)
+ {
+ gcc_assert (k1 != NULL);
+ gcc_assert (k2 != NULL);
+ gcc_assert (k1 != reinterpret_cast<key_type> (1));
+ gcc_assert (k2 != reinterpret_cast<key_type> (1));
+ if (k1 && k2)
+ return *k1 == *k2;
+ else
+ /* Otherwise they must both be non-NULL. */
+ return k1 == k2;
+ }
+ template <typename T>
+ static inline void remove (T &)
+ {
+ /* empty; the nodes are handled elsewhere. */
+ }
+ template <typename T>
+ static inline void mark_deleted (T &entry)
+ {
+ entry.m_key = reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline void mark_empty (T &entry)
+ {
+ entry.m_key = NULL;
+ }
+ template <typename T>
+ static inline bool is_deleted (const T &entry)
+ {
+ return entry.m_key == reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline bool is_empty (const T &entry)
+ {
+ return entry.m_key == NULL;
+ }
+ static const bool empty_zero_p = false;
+};
+
+/* Data about a particular call_string within an exploded_graph. */
+
+struct per_call_string_data
+{
+ per_call_string_data (const call_string &key, int num_supernodes)
+ : m_key (key), m_stats (num_supernodes)
+ {}
+
+ const call_string m_key;
+ stats m_stats;
+};
+
+/* Traits class for storing per-call_string data within
+ an exploded_graph. */
+
+struct eg_call_string_hash_map_traits
+{
+ typedef const call_string *key_type;
+ typedef per_call_string_data *value_type;
+ typedef per_call_string_data *compare_type;
+
+ static inline hashval_t hash (const key_type &k)
+ {
+ gcc_assert (k != NULL);
+ gcc_assert (k != reinterpret_cast<key_type> (1));
+ return k->hash ();
+ }
+ static inline bool equal_keys (const key_type &k1, const key_type &k2)
+ {
+ gcc_assert (k1 != NULL);
+ gcc_assert (k2 != NULL);
+ gcc_assert (k1 != reinterpret_cast<key_type> (1));
+ gcc_assert (k2 != reinterpret_cast<key_type> (1));
+ if (k1 && k2)
+ return *k1 == *k2;
+ else
+ /* Otherwise they must both be non-NULL. */
+ return k1 == k2;
+ }
+ template <typename T>
+ static inline void remove (T &)
+ {
+ /* empty; the nodes are handled elsewhere. */
+ }
+ template <typename T>
+ static inline void mark_deleted (T &entry)
+ {
+ entry.m_key = reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline void mark_empty (T &entry)
+ {
+ entry.m_key = NULL;
+ }
+ template <typename T>
+ static inline bool is_deleted (const T &entry)
+ {
+ return entry.m_key == reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline bool is_empty (const T &entry)
+ {
+ return entry.m_key == NULL;
+ }
+ static const bool empty_zero_p = false;
+};
+
+/* Data about a particular function within an exploded_graph. */
+
+struct per_function_data
+{
+ per_function_data () {}
+
+ void add_call_summary (exploded_node *node)
+ {
+ m_summaries.safe_push (node);
+ }
+
+ auto_vec<exploded_node *> m_summaries;
+};
+
+
+/* The strongly connected components of a supergraph.
+ In particular, this allows us to compute a partial ordering
+ of supernodes. */
+
+class strongly_connected_components
+{
+public:
+ strongly_connected_components (const supergraph &sg, logger *logger);
+
+ int get_scc_id (int node_index) const
+ {
+ return m_per_node[node_index].m_lowlink;
+ }
+
+ void dump () const;
+
+private:
+ struct per_node_data
+ {
+ per_node_data ()
+ : m_index (-1), m_lowlink (-1), m_on_stack (false)
+ {}
+
+ int m_index;
+ int m_lowlink;
+ bool m_on_stack;
+ };
+
+ void strong_connect (unsigned index);
+
+ const supergraph &m_sg;
+ auto_vec<unsigned> m_stack;
+ auto_vec<per_node_data> m_per_node;
+};
+
+/* The worklist of exploded_node instances that have been added to
+ an exploded_graph, but that haven't yet been processed to find
+ their successors (or warnings).
+
+ The enodes are stored in a priority queue, ordered by a topological
+ sort of the SCCs in the supergraph, so that enodes for the same
+ program_point should appear at the front of the queue together.
+ This allows for state-merging at CFG join-points, so that
+ sufficiently-similar enodes can be merged into one. */
+
+class worklist
+{
+public:
+ worklist (const exploded_graph &eg, const analysis_plan &plan);
+ unsigned length () const;
+ exploded_node *take_next ();
+ exploded_node *peek_next ();
+ void add_node (exploded_node *enode);
+
+private:
+ class key_t
+ {
+ public:
+ key_t (const worklist &w, exploded_node *enode)
+ : m_worklist (w), m_enode (enode)
+ {}
+
+ bool operator< (const key_t &other) const
+ {
+ return cmp (*this, other) < 0;
+ }
+
+ bool operator== (const key_t &other) const
+ {
+ return cmp (*this, other) == 0;
+ }
+
+ bool operator> (const key_t &other) const
+ {
+ return !(*this == other || *this < other);
+ }
+
+ private:
+ static int cmp_1 (const key_t &ka, const key_t &kb);
+ static int cmp (const key_t &ka, const key_t &kb);
+
+ int get_scc_id (const exploded_node *enode) const
+ {
+ const supernode *snode = enode->get_supernode ();
+ if (snode == NULL)
+ return 0;
+ return m_worklist.m_scc.get_scc_id (snode->m_index);
+ }
+
+ const worklist &m_worklist;
+ exploded_node *m_enode;
+ };
+
+ /* The order is important here: m_scc needs to stick around
+ until after m_queue has finished being cleaned up (the dtor
+ calls the ordering fns). */
+ const exploded_graph &m_eg;
+ strongly_connected_components m_scc;
+ const analysis_plan &m_plan;
+
+ /* Priority queue, backed by a fibonacci_heap. */
+ typedef fibonacci_heap<key_t, exploded_node> queue_t;
+ queue_t m_queue;
+};
+
+/* An exploded_graph is a directed graph of unique <point, state> pairs.
+ It also has a worklist of nodes that are waiting for their successors
+ to be added to the graph. */
+
+class exploded_graph : public digraph<eg_traits>
+{
+public:
+ typedef hash_map <const call_string *, per_call_string_data *,
+ eg_call_string_hash_map_traits> call_string_data_map_t;
+
+ exploded_graph (const supergraph &sg, logger *logger,
+ const extrinsic_state &ext_state,
+ const state_purge_map *purge_map,
+ const analysis_plan &plan,
+ int verbosity);
+ ~exploded_graph ();
+
+ logger *get_logger () const { return m_logger.get_logger (); }
+
+ const supergraph &get_supergraph () const { return m_sg; }
+ const extrinsic_state &get_ext_state () const { return m_ext_state; }
+ const state_purge_map *get_purge_map () const { return m_purge_map; }
+ const analysis_plan &get_analysis_plan () const { return m_plan; }
+
+ exploded_node *get_origin () const { return m_origin; }
+
+ exploded_node *add_function_entry (function *fun);
+
+ void build_initial_worklist ();
+ void process_worklist ();
+ void process_node (exploded_node *node);
+
+ exploded_node *get_or_create_node (const program_point &point,
+ const program_state &state,
+ state_change *change);
+ exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
+ const superedge *sedge,
+ const state_change &change,
+ exploded_edge::custom_info_t *custom = NULL);
+
+ per_program_point_data *
+ get_or_create_per_program_point_data (const program_point &);
+
+ per_call_string_data *
+ get_or_create_per_call_string_data (const call_string &);
+
+ per_function_data *
+ get_or_create_per_function_data (function *);
+ per_function_data *get_per_function_data (function *) const;
+
+ void save_diagnostic (const state_machine &sm,
+ const exploded_node *enode,
+ const supernode *node, const gimple *stmt,
+ stmt_finder *finder,
+ tree var, state_machine::state_t state,
+ pending_diagnostic *d);
+
+ diagnostic_manager &get_diagnostic_manager ()
+ {
+ return m_diagnostic_manager;
+ }
+
+ stats *get_global_stats () { return &m_global_stats; }
+ stats *get_or_create_function_stats (function *fn);
+ void log_stats () const;
+ void dump_stats (FILE *) const;
+ void dump_states_for_supernode (FILE *, const supernode *snode) const;
+ void dump_exploded_nodes () const;
+
+ const call_string_data_map_t *get_per_call_string_data () const
+ { return &m_per_call_string_data; }
+
+private:
+ DISABLE_COPY_AND_ASSIGN (exploded_graph);
+
+ const supergraph &m_sg;
+
+ log_user m_logger;
+
+ /* Map from point/state to exploded node.
+ To avoid duplication we store point_and_state
+ *pointers* as keys, rather than point_and_state, using the
+ instance from within the exploded_node, with a custom hasher. */
+ typedef hash_map <const point_and_state *, exploded_node *,
+ eg_hash_map_traits> map_t;
+ map_t m_point_and_state_to_node;
+
+ /* Map from program_point to per-program_point data. */
+ typedef hash_map <const program_point *, per_program_point_data *,
+ eg_point_hash_map_traits> point_map_t;
+ point_map_t m_per_point_data;
+
+ worklist m_worklist;
+
+ exploded_node *m_origin;
+
+ const extrinsic_state &m_ext_state;
+
+ const state_purge_map *const m_purge_map;
+
+ const analysis_plan &m_plan;
+
+ typedef hash_map<function *, per_function_data *> per_function_data_t;
+ per_function_data_t m_per_function_data;
+
+ diagnostic_manager m_diagnostic_manager;
+
+ /* Stats. */
+ stats m_global_stats;
+ typedef ordered_hash_map<function *, stats *> function_stat_map_t;
+ function_stat_map_t m_per_function_stats;
+ stats m_functionless_stats;
+
+ call_string_data_map_t m_per_call_string_data;
+
+ auto_vec<int> m_PK_AFTER_SUPERNODE_per_snode;
+};
+
+/* A path within an exploded_graph: a sequence of edges. */
+
+class exploded_path
+{
+public:
+ exploded_path () : m_edges () {}
+ exploded_path (const exploded_path &other);
+ exploded_path & operator= (const exploded_path &other);
+
+ unsigned length () const { return m_edges.length (); }
+
+ bool find_stmt_backwards (const gimple *search_stmt,
+ int *out_idx) const;
+
+ exploded_node *get_final_enode () const;
+
+ void dump_to_pp (pretty_printer *pp) const;
+ void dump (FILE *fp) const;
+ void dump () const;
+
+ bool feasible_p (logger *logger) const;
+
+ auto_vec<const exploded_edge *> m_edges;
+};
+
+/* Finding the shortest exploded_path within an exploded_graph. */
+
+typedef shortest_paths<eg_traits, exploded_path> shortest_exploded_paths;
+
+/* Abstract base class for use when passing NULL as the stmt for
+ a possible warning, allowing the choice of stmt to be deferred
+ until after we have an emission path (and know we're emitting a
+ warning). */
+
+class stmt_finder
+{
+public:
+ virtual ~stmt_finder () {}
+ virtual stmt_finder *clone () const = 0;
+ virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+};
+
+// TODO: split the above up?
+
+#endif /* GCC_ANALYZER_EXPLODED_GRAPH_H */
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
new file mode 100644
index 00000000000..f6c48837a59
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -0,0 +1,70 @@
+/* Classes for analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+
+#if ENABLE_ANALYZER
+
+/* Generate a label_text by printing FMT.
+
+ Use a clone of the global_dc for formatting callbacks.
+
+ Use this evdesc::event_desc's m_colorize flag to control colorization
+ (so that e.g. we can disable it for JSON output). */
+
+label_text
+evdesc::event_desc::formatted_print (const char *fmt, ...) const
+{
+ pretty_printer *pp = global_dc->printer->clone ();
+
+ pp_show_color (pp) = m_colorize;
+
+ text_info ti;
+ rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+ va_list ap;
+ va_start (ap, fmt);
+ ti.format_spec = _(fmt);
+ ti.args_ptr = ≈
+ ti.err_no = 0;
+ ti.x_data = NULL;
+ ti.m_richloc = &rich_loc;
+ pp_format (pp, &ti);
+ pp_output_formatted_text (pp);
+ va_end (ap);
+
+ label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+ delete pp;
+ return result;
+}
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
new file mode 100644
index 00000000000..bb03e752530
--- /dev/null
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -0,0 +1,266 @@
+/* Classes for analyzer diagnostics.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_PENDING_DIAGNOSTIC_H
+#define GCC_ANALYZER_PENDING_DIAGNOSTIC_H
+
+/* Various bundles of information used for generating more precise
+ messages for events within a diagnostic_path, for passing to the
+ various "describe_*" vfuncs of pending_diagnostic. See those
+ for more information. */
+
+namespace evdesc {
+
+struct event_desc
+{
+ event_desc (bool colorize) : m_colorize (colorize) {}
+
+ label_text formatted_print (const char *fmt, ...) const
+ ATTRIBUTE_GCC_DIAG(2,3);
+
+ bool m_colorize;
+};
+
+/* For use by pending_diagnostic::describe_state_change. */
+
+struct state_change : public event_desc
+{
+ state_change (bool colorize,
+ tree expr,
+ tree origin,
+ state_machine::state_t old_state,
+ state_machine::state_t new_state,
+ diagnostic_event_id_t event_id,
+ const state_change_event &event)
+ : event_desc (colorize),
+ m_expr (expr), m_origin (origin),
+ m_old_state (old_state), m_new_state (new_state),
+ m_event_id (event_id), m_event (event)
+ {}
+
+ bool is_global_p () const { return m_expr == NULL_TREE; }
+
+ tree m_expr;
+ tree m_origin;
+ state_machine::state_t m_old_state;
+ state_machine::state_t m_new_state;
+ diagnostic_event_id_t m_event_id;
+ const state_change_event &m_event;
+};
+
+/* For use by pending_diagnostic::describe_call_with_state. */
+
+struct call_with_state : public event_desc
+{
+ call_with_state (bool colorize,
+ tree caller_fndecl, tree callee_fndecl,
+ tree expr, state_machine::state_t state)
+ : event_desc (colorize),
+ m_caller_fndecl (caller_fndecl),
+ m_callee_fndecl (callee_fndecl),
+ m_expr (expr),
+ m_state (state)
+ {
+ }
+
+ tree m_caller_fndecl;
+ tree m_callee_fndecl;
+ tree m_expr;
+ state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_return_of_state. */
+
+struct return_of_state : public event_desc
+{
+ return_of_state (bool colorize,
+ tree caller_fndecl, tree callee_fndecl,
+ state_machine::state_t state)
+ : event_desc (colorize),
+ m_caller_fndecl (caller_fndecl),
+ m_callee_fndecl (callee_fndecl),
+ m_state (state)
+ {
+ }
+
+ tree m_caller_fndecl;
+ tree m_callee_fndecl;
+ state_machine::state_t m_state;
+};
+
+/* For use by pending_diagnostic::describe_final_event. */
+
+struct final_event : public event_desc
+{
+ final_event (bool colorize,
+ tree expr, state_machine::state_t state)
+ : event_desc (colorize),
+ m_expr (expr), m_state (state)
+ {}
+
+ tree m_expr;
+ state_machine::state_t m_state;
+};
+
+} /* end of namespace evdesc */
+
+/* An abstract base class for capturing information about a diagnostic in
+ a form that is ready to emit at a later point (or be rejected).
+ Each kind of diagnostic will have a concrete subclass of
+ pending_diagnostic.
+
+ Normally, gcc diagnostics are emitted using va_list, which can't be
+ portably stored for later use, so we have to use an "emit" virtual
+ function.
+
+ This class also supports comparison, so that multiple pending_diagnostic
+ instances can be de-duplicated.
+
+ As well as emitting a diagnostic, the class has various "precision of
+ wording" virtual functions, for generating descriptions for events
+ within a diagnostic_path. These are optional, but implementing these
+ allows for more precise wordings than the more generic
+ implementation. */
+
+class pending_diagnostic
+{
+ public:
+ virtual ~pending_diagnostic () {}
+
+ /* Vfunc for emitting the diagnostic. The rich_location will have been
+ populated with a diagnostic_path.
+ Return true if a diagnostic is actually emitted. */
+ virtual bool emit (rich_location *) = 0;
+
+ /* Hand-coded RTTI: get an ID for the subclass. */
+ virtual const char *get_kind () const = 0;
+
+ /* Compare for equality with OTHER, which might be of a different
+ subclass. */
+
+ bool equal_p (const pending_diagnostic &other)
+ {
+ /* Check for pointer equality on the IDs from get_kind. */
+ if (get_kind () != other.get_kind ())
+ return false;
+ /* Call vfunc now we know they have the same ID: */
+ return subclass_equal_p (other);
+ }
+
+ /* A vfunc for testing for equality, where we've already
+ checked they have the same ID. See pending_diagnostic_subclass
+ below for a convenience subclass for implementing this. */
+ virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
+
+ /* For greatest precision-of-wording, the various following "describe_*"
+ virtual functions give the pending diagnostic a way to describe events
+ in a diagnostic_path in terms that make sense for that diagnostic.
+
+ In each case, return a non-NULL label_text to give the event a custom
+ description; NULL otherwise (falling back on a more generic
+ description). */
+
+ /* Precision-of-wording vfunc for describing a critical state change
+ within the diagnostic_path.
+
+ For example, a double-free diagnostic might use the descriptions:
+ - "first 'free' happens here"
+ - "second 'free' happens here"
+ for the pertinent events, whereas a use-after-free might use the
+ descriptions:
+ - "freed here"
+ - "use after free here"
+ Note how in both cases the first event is a "free": the best
+ description to use depends on the diagnostic. */
+
+ virtual label_text describe_state_change (const evdesc::state_change &)
+ {
+ /* Default no-op implementation. */
+ return label_text ();
+ }
+
+ /* Precision-of-wording vfunc for describing an interprocedural call
+ carrying critial state for the diagnostic, from caller to callee.
+
+ For example a double-free diagnostic might use:
+ - "passing freed pointer 'ptr' in call to 'deallocator' from 'test'"
+ to make it clearer how the freed value moves from caller to
+ callee. */
+
+ virtual label_text describe_call_with_state (const evdesc::call_with_state &)
+ {
+ /* Default no-op implementation. */
+ return label_text ();
+ }
+
+ /* Precision-of-wording vfunc for describing an interprocedural return
+ within the diagnostic_path that carries critial state for the
+ diagnostic, from callee back to caller.
+
+ For example, a deref-of-unchecked-malloc diagnostic might use:
+ - "returning possibly-NULL pointer to 'make_obj' from 'allocator'"
+ to make it clearer how the unchecked value moves from callee
+ back to caller. */
+
+ virtual label_text describe_return_of_state (const evdesc::return_of_state &)
+ {
+ /* Default no-op implementation. */
+ return label_text ();
+ }
+
+ /* Precision-of-wording vfunc for describing the final event within a
+ diagnostic_path.
+
+ For example a double-free diagnostic might use:
+ - "second 'free' here; first 'free' was at (3)"
+ and a use-after-free might use
+ - "use after 'free' here; memory was freed at (2)". */
+
+ virtual label_text describe_final_event (const evdesc::final_event &)
+ {
+ /* Default no-op implementation. */
+ return label_text ();
+ }
+
+ /* End of precision-of-wording vfuncs. */
+};
+
+/* A template to make it easier to make subclasses of pending_diagnostic.
+
+ This uses the curiously-recurring template pattern, to implement
+ pending_diagnostic::subclass_equal_p by casting and calling
+ the operator==
+
+ This assumes that BASE_OTHER has already been checked to have
+ been of the same subclass (which pending_diagnostic::equal_p does). */
+
+template <class Subclass>
+class pending_diagnostic_subclass : public pending_diagnostic
+{
+ public:
+ bool subclass_equal_p (const pending_diagnostic &base_other) const
+ FINAL OVERRIDE
+ {
+ const Subclass &other = (const Subclass &)base_other;
+ return *(const Subclass*)this == other;
+ }
+};
+
+#endif /* GCC_ANALYZER_PENDING_DIAGNOSTIC_H */
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
new file mode 100644
index 00000000000..e85f1ba49f0
--- /dev/null
+++ b/gcc/analyzer/program-point.cc
@@ -0,0 +1,554 @@
+/* Classes for representing locations within the program.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "gimple-pretty-print.h"
+#include "gcc-rich-location.h"
+#include "analyzer/call-string.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "function.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "digraph.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/program-point.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/region-model.h"
+#include "analyzer/sm.h"
+#include "analyzer/program-state.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "shortest-paths.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/analysis-plan.h"
+
+#if ENABLE_ANALYZER
+
+/* Get a string for PK. */
+
+const char *
+point_kind_to_string (enum point_kind pk)
+{
+ switch (pk)
+ {
+ default:
+ gcc_unreachable ();
+ case PK_ORIGIN:
+ return "PK_ORIGIN";
+ case PK_BEFORE_SUPERNODE:
+ return "PK_BEFORE_SUPERNODE";
+ case PK_BEFORE_STMT:
+ return "PK_BEFORE_STMT";
+ case PK_AFTER_SUPERNODE:
+ return "PK_AFTER_SUPERNODE";
+ case PK_EMPTY:
+ return "PK_EMPTY";
+ case PK_DELETED:
+ return "PK_DELETED";
+ }
+}
+
+/* class function_point. */
+
+/* Print this function_point to PP. */
+
+void
+function_point::print (pretty_printer *pp, const format &f) const
+{
+ switch (get_kind ())
+ {
+ default:
+ gcc_unreachable ();
+
+ case PK_ORIGIN:
+ pp_printf (pp, "origin");
+ break;
+
+ case PK_BEFORE_SUPERNODE:
+ {
+ if (m_from_edge)
+ pp_printf (pp, "before SN: %i (from SN: %i)",
+ m_supernode->m_index, m_from_edge->m_src->m_index);
+ else
+ pp_printf (pp, "before SN: %i (NULL from-edge)",
+ m_supernode->m_index);
+ f.spacer (pp);
+ for (gphi_iterator gpi
+ = const_cast<supernode *>(get_supernode ())->start_phis ();
+ !gsi_end_p (gpi); gsi_next (&gpi))
+ {
+ const gphi *phi = gpi.phi ();
+ pp_gimple_stmt_1 (pp, phi, 0, (dump_flags_t)0);
+ }
+ }
+ break;
+
+ case PK_BEFORE_STMT:
+ pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
+ m_stmt_idx);
+ f.spacer (pp);
+ pp_gimple_stmt_1 (pp, get_stmt (), 0, (dump_flags_t)0);
+ if (f.m_newlines)
+ {
+ pp_newline (pp);
+ print_source_line (pp);
+ }
+ break;
+
+ case PK_AFTER_SUPERNODE:
+ pp_printf (pp, "after SN: %i", m_supernode->m_index);
+ break;
+ }
+}
+
+/* Generate a hash value for this function_point. */
+
+hashval_t
+function_point::hash () const
+{
+ inchash::hash hstate;
+ if (m_supernode)
+ hstate.add_int (m_supernode->m_index);
+ hstate.add_ptr (m_from_edge);
+ hstate.add_int (m_stmt_idx);
+ hstate.add_int (m_kind);
+ return hstate.end ();
+}
+
+/* Get the gimple stmt for this function_point, if any. */
+
+const gimple *
+function_point::get_stmt () const
+{
+ if (m_kind == PK_BEFORE_STMT)
+ return m_supernode->m_stmts[m_stmt_idx];
+ else if (m_kind == PK_AFTER_SUPERNODE)
+ return m_supernode->get_last_stmt ();
+ else
+ return NULL;
+}
+
+/* Get a location for this function_point, if any. */
+
+location_t
+function_point::get_location () const
+{
+ const gimple *stmt = get_stmt ();
+ if (stmt)
+ return stmt->location;
+
+ return UNKNOWN_LOCATION;
+}
+
+/* A subclass of diagnostic_context for use by
+ program_point::print_source_line. */
+
+class debug_diagnostic_context : public diagnostic_context
+{
+public:
+ debug_diagnostic_context ()
+ {
+ diagnostic_initialize (this, 0);
+ show_line_numbers_p = true;
+ show_caret = true;
+ }
+ ~debug_diagnostic_context ()
+ {
+ diagnostic_finish (this);
+ }
+};
+
+/* Print the source line (if any) for this function_point to PP. */
+
+void
+function_point::print_source_line (pretty_printer *pp) const
+{
+ const gimple *stmt = get_stmt ();
+ if (!stmt)
+ return;
+ // TODO: monospace font
+ debug_diagnostic_context tmp_dc;
+ gcc_rich_location richloc (stmt->location);
+ diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
+ pp_string (pp, pp_formatted_text (tmp_dc.printer));
+}
+
+/* class program_point. */
+
+/* Print this program_point to PP. */
+
+void
+program_point::print (pretty_printer *pp, const format &f) const
+{
+ pp_string (pp, "callstring: ");
+ m_call_string.print (pp);
+ f.spacer (pp);
+
+ m_function_point.print (pp, f);
+}
+
+/* Dump this point to stderr. */
+
+DEBUG_FUNCTION void
+program_point::dump () const
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ print (&pp, format (true));
+ pp_flush (&pp);
+}
+
+/* Generate a hash value for this program_point. */
+
+hashval_t
+program_point::hash () const
+{
+ inchash::hash hstate;
+ hstate.merge_hash (m_function_point.hash ());
+ hstate.merge_hash (m_call_string.hash ());
+ return hstate.end ();
+}
+
+/* Get the function * at DEPTH within the call stack. */
+
+function *
+program_point::get_function_at_depth (unsigned depth) const
+{
+ gcc_assert (depth <= m_call_string.length ());
+ if (depth == m_call_string.length ())
+ return m_function_point.get_function ();
+ else
+ return m_call_string[depth]->get_caller_function ();
+}
+
+/* Assert that this object is sane. */
+
+void
+program_point::validate () const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ m_call_string.validate ();
+ /* The "callee" of the final entry in the callstring should be the
+ function of the m_function_point. */
+ if (m_call_string.length () > 0)
+ gcc_assert (m_call_string[m_call_string.length () - 1]->get_callee_function ()
+ == get_function ());
+}
+
+/* Check to see if SUCC is a valid edge to take (ensuring that we have
+ interprocedurally valid paths in the exploded graph, and enforcing
+ recursion limits).
+
+ Update the call string if SUCC is a call or a return.
+
+ Return true if SUCC can be taken, or false otherwise.
+
+ This is the "point" half of exploded_node::on_edge. */
+
+bool
+program_point::on_edge (exploded_graph &eg,
+ const superedge *succ)
+{
+ logger * const logger = eg.get_logger ();
+ LOG_FUNC (logger);
+ switch (succ->m_kind)
+ {
+ case SUPEREDGE_CFG_EDGE:
+ {
+ const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (succ);
+
+ /* Reject abnormal edges; we special-case setjmp/longjmp. */
+ if (cfg_sedge->get_flags () & EDGE_ABNORMAL)
+ return false;
+ }
+ break;
+
+ case SUPEREDGE_CALL:
+ {
+ const call_superedge *call_sedge = as_a <const call_superedge *> (succ);
+
+ if (eg.get_analysis_plan ().use_summary_p (call_sedge->m_cedge))
+ {
+ if (logger)
+ logger->log ("rejecting call edge: using summary instead");
+ return false;
+ }
+
+ /* Add the callsite to the call string. */
+ m_call_string.push_call (eg.get_supergraph (), call_sedge);
+
+ /* Impose a maximum recursion depth and don't analyze paths
+ that exceed it further.
+ This is something of a blunt workaround, but it only
+ applies to recursion (and mutual recursion), not to
+ general call stacks. */
+ if (m_call_string.calc_recursion_depth ()
+ > param_analyzer_max_recursion_depth)
+ {
+ if (logger)
+ logger->log ("rejecting call edge: recursion limit exceeded");
+ // TODO: issue a sorry for this?
+ return false;
+ }
+ }
+ break;
+
+ case SUPEREDGE_RETURN:
+ {
+ /* Require that we return to the call site in the call string. */
+ if (m_call_string.empty_p ())
+ {
+ if (logger)
+ logger->log ("rejecting return edge: empty call string");
+ return false;
+ }
+ const return_superedge *top_of_stack = m_call_string.pop ();
+ if (top_of_stack != succ)
+ {
+ if (logger)
+ logger->log ("rejecting return edge: return to wrong callsite");
+ return false;
+ }
+ }
+ break;
+
+ case SUPEREDGE_INTRAPROCEDURAL_CALL:
+ {
+ const callgraph_superedge *cg_sedge
+ = as_a <const callgraph_superedge *> (succ);
+ /* Consider turning this edge into a use of an
+ interprocedural summary. */
+ if (eg.get_analysis_plan ().use_summary_p (cg_sedge->m_cedge))
+ {
+ if (logger)
+ logger->log ("using function summary for %qE in %qE",
+ cg_sedge->get_callee_decl (),
+ cg_sedge->get_caller_decl ());
+ return true;
+ }
+ else
+ {
+ /* Otherwise, we ignore these edges */
+ if (logger)
+ logger->log ("rejecting interprocedural edge");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* Comparator for program points within the same supernode,
+ for implementing worklist::key_t comparison operators.
+ Return negative if POINT_A is before POINT_B
+ Return positive if POINT_A is after POINT_B
+ Return 0 if they are equal. */
+
+int
+function_point::cmp_within_supernode_1 (const function_point &point_a,
+ const function_point &point_b)
+{
+ gcc_assert (point_a.get_supernode () == point_b.get_supernode ());
+
+ switch (point_a.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case PK_BEFORE_SUPERNODE:
+ switch (point_b.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case PK_BEFORE_SUPERNODE:
+ {
+ int a_src_idx = -1;
+ int b_src_idx = -1;
+ if (point_a.m_from_edge)
+ a_src_idx = point_a.m_from_edge->m_src->m_index;
+ if (point_b.m_from_edge)
+ b_src_idx = point_b.m_from_edge->m_src->m_index;
+ return a_src_idx - b_src_idx;
+ }
+ break;
+
+ case PK_BEFORE_STMT:
+ case PK_AFTER_SUPERNODE:
+ return -1;
+ }
+ break;
+ case PK_BEFORE_STMT:
+ switch (point_b.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case PK_BEFORE_SUPERNODE:
+ return 1;
+
+ case PK_BEFORE_STMT:
+ return point_a.m_stmt_idx - point_b.m_stmt_idx;
+
+ case PK_AFTER_SUPERNODE:
+ return -1;
+ }
+ break;
+ case PK_AFTER_SUPERNODE:
+ switch (point_b.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case PK_BEFORE_SUPERNODE:
+ case PK_BEFORE_STMT:
+ return 1;
+
+ case PK_AFTER_SUPERNODE:
+ return 0;
+ }
+ break;
+ }
+}
+
+/* Comparator for program points within the same supernode,
+ for implementing worklist::key_t comparison operators.
+ Return negative if POINT_A is before POINT_B
+ Return positive if POINT_A is after POINT_B
+ Return 0 if they are equal. */
+
+int
+function_point::cmp_within_supernode (const function_point &point_a,
+ const function_point &point_b)
+{
+ int result = cmp_within_supernode_1 (point_a, point_b);
+
+ /* Check that the ordering is symmetric */
+#if CHECKING_P
+ int reversed = cmp_within_supernode_1 (point_b, point_a);
+ gcc_assert (reversed == -result);
+#endif
+
+ return result;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify that function_point::operator== works as expected. */
+
+static void
+test_function_point_equality ()
+{
+ const supernode *snode = NULL;
+
+ function_point a = function_point (snode, NULL, 0,
+ PK_BEFORE_SUPERNODE);
+ function_point b = function_point::before_supernode (snode, NULL);
+ ASSERT_EQ (a, b);
+}
+
+/* Verify that function_point::cmp_within_supernode works as expected. */
+
+static void
+test_function_point_ordering ()
+{
+ const supernode *snode = NULL;
+ const call_string call_string;
+
+ /* Populate an array with various points within the same
+ snode, in order. */
+ auto_vec<function_point> points;
+ points.safe_push (function_point::before_supernode (snode, NULL));
+ points.safe_push (function_point::before_stmt (snode, 0));
+ points.safe_push (function_point::before_stmt (snode, 1));
+ points.safe_push (function_point::after_supernode (snode));
+
+ /* Check all pairs. */
+ unsigned i;
+ function_point *point_a;
+ FOR_EACH_VEC_ELT (points, i, point_a)
+ {
+ unsigned j;
+ function_point *point_b;
+ FOR_EACH_VEC_ELT (points, j, point_b)
+ {
+ int cmp = function_point::cmp_within_supernode (*point_a, *point_b);
+ if (i == j)
+ ASSERT_EQ (cmp, 0);
+ if (i < j)
+ ASSERT_TRUE (cmp < 0);
+ if (i > j)
+ ASSERT_TRUE (cmp > 0);
+ }
+ }
+}
+
+/* Verify that program_point::operator== works as expected. */
+
+static void
+test_program_point_equality ()
+{
+ const supernode *snode = NULL;
+
+ const call_string cs;
+
+ program_point a = program_point::before_supernode (snode, NULL,
+ cs);
+
+ program_point b = program_point::before_supernode (snode, NULL,
+ cs);
+
+ ASSERT_EQ (a, b);
+ // TODO: verify with non-empty callstrings, with different edges
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_program_point_cc_tests ()
+{
+ test_function_point_equality ();
+ test_function_point_ordering ();
+ test_program_point_equality ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h
new file mode 100644
index 00000000000..b9fe548f3a6
--- /dev/null
+++ b/gcc/analyzer/program-point.h
@@ -0,0 +1,310 @@
+/* Classes for representing locations within the program.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_PROGRAM_POINT_H
+#define GCC_ANALYZER_PROGRAM_POINT_H
+
+class exploded_graph;
+
+/* An enum for distinguishing the various kinds of program_point. */
+
+enum point_kind {
+ /* A "fake" node which has edges to all entrypoints. */
+ PK_ORIGIN,
+
+ PK_BEFORE_SUPERNODE,
+ PK_BEFORE_STMT,
+ PK_AFTER_SUPERNODE,
+
+ /* Special values used for hash_map: */
+ PK_EMPTY,
+ PK_DELETED,
+
+ NUM_POINT_KINDS
+};
+
+extern const char *point_kind_to_string (enum point_kind pk);
+
+class format
+{
+public:
+ format (bool newlines) : m_newlines (newlines) {}
+
+ void spacer (pretty_printer *pp) const
+ {
+ if (m_newlines)
+ pp_newline (pp);
+ else
+ pp_space (pp);
+ }
+
+ bool m_newlines;
+};
+
+/* A class for representing a location within the program, without
+ interprocedural information.
+
+ This represents a fine-grained location within the supergraph (or
+ within one of its nodes). */
+
+class function_point
+{
+public:
+ function_point (const supernode *supernode,
+ const superedge *from_edge,
+ unsigned stmt_idx,
+ enum point_kind kind)
+ : m_supernode (supernode), m_from_edge (from_edge),
+ m_stmt_idx (stmt_idx), m_kind (kind)
+ {
+ if (from_edge)
+ {
+ gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
+ gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
+ }
+ if (stmt_idx)
+ gcc_checking_assert (m_kind == PK_BEFORE_STMT);
+ }
+
+ void print (pretty_printer *pp, const format &f) const;
+ void print_source_line (pretty_printer *pp) const;
+ void dump () const;
+
+ hashval_t hash () const;
+ bool operator== (const function_point &other) const
+ {
+ return (m_supernode == other.m_supernode
+ && m_from_edge == other.m_from_edge
+ && m_stmt_idx == other.m_stmt_idx
+ && m_kind == other.m_kind);
+ }
+
+ /* Accessors. */
+
+ const supernode *get_supernode () const { return m_supernode; }
+ function *get_function () const
+ {
+ if (m_supernode)
+ return m_supernode->m_fun;
+ else
+ return NULL;
+ }
+ const gimple *get_stmt () const;
+ location_t get_location () const;
+ enum point_kind get_kind () const { return m_kind; }
+ const superedge *get_from_edge () const
+ {
+ return m_from_edge;
+ }
+ unsigned get_stmt_idx () const
+ {
+ gcc_assert (m_kind == PK_BEFORE_STMT);
+ return m_stmt_idx;
+ }
+
+ /* Factory functions for making various kinds of program_point. */
+
+ static function_point from_function_entry (const supergraph &sg,
+ function *fun)
+ {
+ return before_supernode (sg.get_node_for_function_entry (fun),
+ NULL);
+ }
+
+ static function_point before_supernode (const supernode *supernode,
+ const superedge *from_edge)
+ {
+ if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
+ from_edge = NULL;
+ return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
+ }
+
+ static function_point before_stmt (const supernode *supernode,
+ unsigned stmt_idx)
+ {
+ return function_point (supernode, NULL, stmt_idx, PK_BEFORE_STMT);
+ }
+
+ static function_point after_supernode (const supernode *supernode)
+ {
+ return function_point (supernode, NULL, 0, PK_AFTER_SUPERNODE);
+ }
+
+ /* Support for hash_map. */
+
+ static function_point empty ()
+ {
+ return function_point (NULL, NULL, 0, PK_EMPTY);
+ }
+ static function_point deleted ()
+ {
+ return function_point (NULL, NULL, 0, PK_DELETED);
+ }
+
+ static int cmp_within_supernode_1 (const function_point &point_a,
+ const function_point &point_b);
+ static int cmp_within_supernode (const function_point &point_a,
+ const function_point &point_b);
+
+ private:
+ const supernode *m_supernode;
+
+ /* For PK_BEFORE_SUPERNODE, and only for CFG edges. */
+ const superedge *m_from_edge;
+
+ /* Only for PK_BEFORE_STMT. */
+ unsigned m_stmt_idx;
+
+ enum point_kind m_kind;
+};
+
+/* A class for representing a location within the program, including
+ interprocedural information.
+
+ This represents a fine-grained location within the supergraph (or
+ within one of its nodes), along with a call string giving the
+ interprocedural context. */
+
+class program_point
+{
+public:
+ program_point (const function_point &fn_point,
+ const call_string &call_string)
+ : m_function_point (fn_point),
+ m_call_string (call_string)
+ {
+ }
+
+ void print (pretty_printer *pp, const format &f) const;
+ void print_source_line (pretty_printer *pp) const;
+ void dump () const;
+
+ hashval_t hash () const;
+ bool operator== (const program_point &other) const
+ {
+ return (m_function_point == other.m_function_point
+ && m_call_string == other.m_call_string);
+ }
+
+ /* Accessors. */
+
+ const function_point &get_function_point () const { return m_function_point; }
+ const call_string &get_call_string () const { return m_call_string; }
+
+ const supernode *get_supernode () const
+ {
+ return m_function_point.get_supernode ();
+ }
+ function *get_function () const
+ {
+ return m_function_point.get_function ();
+ }
+ function *get_function_at_depth (unsigned depth) const;
+ tree get_fndecl () const
+ {
+ gcc_assert (get_kind () != PK_ORIGIN);
+ return get_function ()->decl;
+ }
+ const gimple *get_stmt () const
+ {
+ return m_function_point.get_stmt ();
+ }
+ location_t get_location () const
+ {
+ return m_function_point.get_location ();
+ }
+ enum point_kind get_kind () const
+ {
+ return m_function_point.get_kind ();
+ }
+ const superedge *get_from_edge () const
+ {
+ return m_function_point.get_from_edge ();
+ }
+ unsigned get_stmt_idx () const
+ {
+ return m_function_point.get_stmt_idx ();
+ }
+
+ /* Get the number of frames we expect at this program point.
+ This will be one more than the length of the call_string
+ (which stores the parent callsites), apart from the origin
+ node, which doesn't have any frames. */
+ int get_stack_depth () const
+ {
+ if (get_kind () == PK_ORIGIN)
+ return 0;
+ return m_call_string.length () + 1;
+ }
+
+ /* Factory functions for making various kinds of program_point. */
+
+ static program_point from_function_entry (const supergraph &sg,
+ function *fun)
+ {
+ return program_point (function_point::from_function_entry (sg, fun),
+ call_string ());
+ }
+
+ static program_point before_supernode (const supernode *supernode,
+ const superedge *from_edge,
+ const call_string &call_string)
+ {
+ return program_point (function_point::before_supernode (supernode,
+ from_edge),
+ call_string);
+ }
+
+ static program_point before_stmt (const supernode *supernode,
+ unsigned stmt_idx,
+ const call_string &call_string)
+ {
+ return program_point (function_point::before_stmt (supernode, stmt_idx),
+ call_string);
+ }
+
+ static program_point after_supernode (const supernode *supernode,
+ const call_string &call_string)
+ {
+ return program_point (function_point::after_supernode (supernode),
+ call_string);
+ }
+
+ /* Support for hash_map. */
+
+ static program_point empty ()
+ {
+ return program_point (function_point::empty (), call_string ());
+ }
+ static program_point deleted ()
+ {
+ return program_point (function_point::deleted (), call_string ());
+ }
+
+ bool on_edge (exploded_graph &eg, const superedge *succ);
+
+ void validate () const;
+
+ private:
+ const function_point m_function_point;
+ call_string m_call_string;
+};
+
+#endif /* GCC_ANALYZER_PROGRAM_POINT_H */
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
new file mode 100644
index 00000000000..04346ae9dc8
--- /dev/null
+++ b/gcc/analyzer/program-state.cc
@@ -0,0 +1,1356 @@
+/* Classes for representing the state of interest at a given path of analysis.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/sm.h"
+#include "sbitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "analyzer/region-model.h"
+#include "analyzer/program-state.h"
+#include "analyzer/constraint-manager.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "analyzer/constraint-manager.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/state-purge.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if ENABLE_ANALYZER
+
+/* class sm_state_map. */
+
+/* sm_state_map's ctor. */
+
+sm_state_map::sm_state_map ()
+: m_map (), m_global_state (0)
+{
+}
+
+/* Clone the sm_state_map. */
+
+sm_state_map *
+sm_state_map::clone () const
+{
+ return new sm_state_map (*this);
+}
+
+/* Clone this sm_state_map, remapping all svalue_ids within it with ID_MAP.
+
+ Return NULL if there are any svalue_ids that have sm-state for which
+ ID_MAP maps them to svalue_id::null (and thus the clone would have lost
+ the sm-state information). */
+
+sm_state_map *
+sm_state_map::clone_with_remapping (const one_way_svalue_id_map &id_map) const
+{
+ sm_state_map *result = new sm_state_map ();
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ svalue_id sid = (*iter).first;
+ gcc_assert (!sid.null_p ());
+ entry_t e = (*iter).second;
+ /* TODO: what should we do if the origin maps from non-null to null?
+ Is that loss of information acceptable? */
+ id_map.update (&e.m_origin);
+
+ svalue_id new_sid = id_map.get_dst_for_src (sid);
+ if (new_sid.null_p ())
+ {
+ delete result;
+ return NULL;
+ }
+ result->m_map.put (new_sid, e);
+ }
+ return result;
+}
+
+/* Print this sm_state_map (for SM) to PP. */
+
+void
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
+{
+ bool first = true;
+ pp_string (pp, "{");
+ if (m_global_state != 0)
+ {
+ pp_printf (pp, "global: %s", sm.get_state_name (m_global_state));
+ first = false;
+ }
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ if (!first)
+ pp_string (pp, ", ");
+ first = false;
+ svalue_id sid = (*iter).first;
+ sid.print (pp);
+
+ entry_t e = (*iter).second;
+ pp_printf (pp, ": %s (origin: ",
+ sm.get_state_name (e.m_state));
+ e.m_origin.print (pp);
+ pp_string (pp, ")");
+ }
+ pp_string (pp, "}");
+}
+
+/* Dump this object (for SM) to stderr. */
+
+DEBUG_FUNCTION void
+sm_state_map::dump (const state_machine &sm) const
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ print (sm, &pp);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+/* Return true if no states have been set within this map
+ (all expressions are for the start state). */
+
+bool
+sm_state_map::is_empty_p () const
+{
+ return m_map.elements () == 0 && m_global_state == 0;
+}
+
+/* Generate a hash value for this sm_state_map. */
+
+hashval_t
+sm_state_map::hash () const
+{
+ hashval_t result = 0;
+
+ /* Accumulate the result by xoring a hash for each slot, so that the
+ result doesn't depend on the ordering of the slots in the map. */
+
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ inchash::hash hstate;
+ inchash::add ((*iter).first, hstate);
+ entry_t e = (*iter).second;
+ hstate.add_int (e.m_state);
+ inchash::add (e.m_origin, hstate);
+ result ^= hstate.end ();
+ }
+ result ^= m_global_state;
+
+ return result;
+}
+
+/* Equality operator for sm_state_map. */
+
+bool
+sm_state_map::operator== (const sm_state_map &other) const
+{
+ if (m_global_state != other.m_global_state)
+ return false;
+
+ if (m_map.elements () != other.m_map.elements ())
+ return false;
+
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ svalue_id sid = (*iter).first;
+ entry_t e = (*iter).second;
+ entry_t *other_slot = const_cast <map_t &> (other.m_map).get (sid);
+ if (other_slot == NULL)
+ return false;
+ if (e != *other_slot)
+ return false;
+ }
+
+ gcc_checking_assert (hash () == other.hash ());
+
+ return true;
+}
+
+/* Get the state of SID within this object.
+ States default to the start state. */
+
+state_machine::state_t
+sm_state_map::get_state (svalue_id sid) const
+{
+ gcc_assert (!sid.null_p ());
+
+ if (entry_t *slot
+ = const_cast <map_t &> (m_map).get (sid))
+ return slot->m_state;
+ else
+ return 0;
+}
+
+/* Get the "origin" svalue_id for any state of SID. */
+
+svalue_id
+sm_state_map::get_origin (svalue_id sid) const
+{
+ gcc_assert (!sid.null_p ());
+
+ entry_t *slot
+ = const_cast <map_t &> (m_map).get (sid);
+ if (slot)
+ return slot->m_origin;
+ else
+ return svalue_id::null ();
+}
+
+/* Set the state of SID within MODEL to STATE, recording that
+ the state came from ORIGIN. */
+
+void
+sm_state_map::set_state (region_model *model,
+ svalue_id sid,
+ state_machine::state_t state,
+ svalue_id origin)
+{
+ if (model == NULL)
+ return;
+ equiv_class &ec = model->get_constraints ()->get_equiv_class (sid);
+ set_state (ec, state, origin);
+
+ /* Also do it for all svalues that are equal via non-cm, so that
+ e.g. (void *)&r and (foo *)&r transition together. */
+ for (unsigned i = 0; i < model->get_num_svalues (); i++)
+ {
+ svalue_id other_sid = svalue_id::from_int (i);
+ if (other_sid == sid)
+ continue;
+
+ tristate eq = model->eval_condition_without_cm (sid, EQ_EXPR, other_sid);
+ if (eq.is_true ())
+ impl_set_state (other_sid, state, origin);
+ }
+}
+
+/* Set the state of EC to STATE, recording that the state came from
+ ORIGIN. */
+
+void
+sm_state_map::set_state (const equiv_class &ec,
+ state_machine::state_t state,
+ svalue_id origin)
+{
+ int i;
+ svalue_id *sid;
+ FOR_EACH_VEC_ELT (ec.m_vars, i, sid)
+ impl_set_state (*sid, state, origin);
+}
+
+/* Set state of PV to STATE, bypassing equivalence classes. */
+
+void
+sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
+ svalue_id origin)
+{
+ /* Special-case state 0 as the default value. */
+ if (state == 0)
+ {
+ if (m_map.get (sid))
+ m_map.remove (sid);
+ return;
+ }
+ gcc_assert (!sid.null_p ());
+ m_map.put (sid, entry_t (state, origin));
+}
+
+/* Set the "global" state within this state map to STATE. */
+
+void
+sm_state_map::set_global_state (state_machine::state_t state)
+{
+ m_global_state = state;
+}
+
+/* Get the "global" state within this state map. */
+
+state_machine::state_t
+sm_state_map::get_global_state () const
+{
+ return m_global_state;
+}
+
+/* Handle CALL to unknown FNDECL with an unknown function body, which
+ could do anything to the states passed to it.
+ Clear any state for SM for the params and any LHS.
+ Note that the function might be known to other state machines, but
+ not to this one. */
+
+void
+sm_state_map::purge_for_unknown_fncall (const exploded_graph &eg,
+ const state_machine &sm,
+ const gcall *call,
+ tree fndecl,
+ region_model *new_model)
+{
+ logger * const logger = eg.get_logger ();
+ if (logger)
+ {
+ if (fndecl)
+ logger->log ("function %qE is unknown to checker %qs",
+ fndecl, sm.get_name ());
+ else
+ logger->log ("unknown function pointer for checker %qs",
+ sm.get_name ());
+ }
+
+ /* Purge any state for parms. */
+ tree iter_param_types = NULL_TREE;
+ if (fndecl)
+ iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+ for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+ {
+ /* Track expected param type, where available. */
+ if (iter_param_types)
+ {
+ tree param_type = TREE_VALUE (iter_param_types);
+ gcc_assert (param_type);
+ iter_param_types = TREE_CHAIN (iter_param_types);
+
+ /* Don't purge state if it was passed as a const pointer
+ e.g. for things like strlen (PTR). */
+ if (TREE_CODE (param_type) == POINTER_TYPE)
+ if (TYPE_READONLY (TREE_TYPE (param_type)))
+ continue;
+ }
+ tree parm = gimple_call_arg (call, arg_idx);
+ svalue_id parm_sid = new_model->get_rvalue (parm, NULL);
+ set_state (new_model, parm_sid, 0, svalue_id::null ());
+
+ /* Also clear sm-state from svalue_ids that are passed via a
+ pointer. */
+ if (TREE_CODE (parm) == ADDR_EXPR)
+ {
+ tree pointee = TREE_OPERAND (parm, 0);
+ svalue_id parm_sid = new_model->get_rvalue (pointee, NULL);
+ set_state (new_model, parm_sid, 0, svalue_id::null ());
+ }
+ }
+
+ /* Purge any state for any LHS. */
+ if (tree lhs = gimple_call_lhs (call))
+ {
+ svalue_id lhs_sid = new_model->get_rvalue (lhs, NULL);
+ set_state (new_model, lhs_sid, 0, svalue_id::null ());
+ }
+}
+
+/* Update this map based on MAP. */
+
+void
+sm_state_map::remap_svalue_ids (const svalue_id_map &map)
+{
+ map_t tmp_map;
+
+ /* Build an intermediate map, using the new sids. */
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ svalue_id sid = (*iter).first;
+ entry_t e = (*iter).second;
+
+ map.update (&sid);
+ map.update (&e.m_origin);
+ tmp_map.put (sid, e);
+ }
+
+ /* Clear the existing values. */
+ m_map.empty ();
+
+ /* Copy over from intermediate map. */
+ for (typename map_t::iterator iter = tmp_map.begin ();
+ iter != tmp_map.end ();
+ ++iter)
+ {
+ svalue_id sid = (*iter).first;
+ entry_t e = (*iter).second;
+
+ impl_set_state (sid, e.m_state, e.m_origin);
+ }
+}
+
+/* Purge any state for svalue_ids >= FIRST_UNUSED_SID.
+ If !SM::can_purge_p, then report the state as leaking,
+ using SM_IDX, CTXT, and MAP.
+ Return the number of states that were purged. */
+
+int
+sm_state_map::on_svalue_purge (const state_machine &sm,
+ int sm_idx,
+ svalue_id first_unused_sid,
+ const svalue_id_map &map,
+ impl_region_model_context *ctxt)
+{
+ /* TODO: ideally remove the slot directly; for now
+ do it in two stages. */
+ auto_vec<svalue_id> to_remove;
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ svalue_id dst_sid ((*iter).first);
+ if (dst_sid.as_int () >= first_unused_sid.as_int ())
+ {
+ /* Complain about leaks here. */
+ entry_t e = (*iter).second;
+
+ if (!sm.can_purge_p (e.m_state))
+ ctxt->on_state_leak (sm, sm_idx, dst_sid, first_unused_sid,
+ map, e.m_state);
+
+ to_remove.safe_push (dst_sid);
+ }
+ }
+
+ int i;
+ svalue_id *dst_sid;
+ FOR_EACH_VEC_ELT (to_remove, i, dst_sid)
+ m_map.remove (*dst_sid);
+
+ return to_remove.length ();
+}
+
+/* Set the state of CHILD_SID to that of PARENT_SID. */
+
+void
+sm_state_map::on_inherited_svalue (svalue_id parent_sid,
+ svalue_id child_sid)
+{
+ state_machine::state_t state = get_state (parent_sid);
+ impl_set_state (child_sid, state, parent_sid);
+}
+
+/* Set the state of DST_SID to that of SRC_SID. */
+
+void
+sm_state_map::on_cast (svalue_id src_sid,
+ svalue_id dst_sid)
+{
+ state_machine::state_t state = get_state (src_sid);
+ impl_set_state (dst_sid, state, get_origin (src_sid));
+}
+
+/* Assert that this object is sane. */
+
+void
+sm_state_map::validate (const state_machine &sm,
+ int num_svalues) const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ for (typename map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ svalue_id sid = (*iter).first;
+ entry_t e = (*iter).second;
+
+ gcc_assert (sid.as_int () < num_svalues);
+ sm.validate (e.m_state);
+ gcc_assert (e.m_origin.as_int () < num_svalues);
+ }
+}
+
+/* class program_state. */
+
+/* program_state's ctor. */
+
+program_state::program_state (const extrinsic_state &ext_state)
+: m_region_model (new region_model ()),
+ m_checker_states (ext_state.m_checkers.length ())
+{
+ int num_states = ext_state.m_checkers.length ();
+ for (int i = 0; i < num_states; i++)
+ m_checker_states.quick_push (new sm_state_map ());
+}
+
+/* program_state's copy ctor. */
+
+program_state::program_state (const program_state &other)
+: m_region_model (new region_model (*other.m_region_model)),
+ m_checker_states (other.m_checker_states.length ())
+{
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+ m_checker_states.quick_push (smap->clone ());
+}
+
+/* program_state's assignment operator. */
+
+program_state&
+program_state::operator= (const program_state &other)
+{
+ delete m_region_model;
+ m_region_model = new region_model (*other.m_region_model);
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ delete smap;
+ m_checker_states.truncate (0);
+ gcc_assert (m_checker_states.space (other.m_checker_states.length ()));
+
+ FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+ m_checker_states.quick_push (smap->clone ());
+
+ return *this;
+}
+
+#if __cplusplus >= 201103
+/* Move constructor for program_state (when building with C++11). */
+program_state::program_state (program_state &&other)
+: m_region_model (other.m_region_model),
+ m_checker_states (other.m_checker_states.length ())
+{
+ other.m_region_model = NULL;
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
+ m_checker_states.quick_push (smap);
+ other.m_checker_states.truncate (0);
+}
+#endif
+
+/* program_state's dtor. */
+
+program_state::~program_state ()
+{
+ delete m_region_model;
+}
+
+/* Generate a hash value for this program_state. */
+
+hashval_t
+program_state::hash () const
+{
+ hashval_t result = m_region_model->hash ();
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ result ^= smap->hash ();
+ return result;
+}
+
+/* Equality operator for program_state.
+ All parts of the program_state (region model, checker states) must
+ equal their counterparts in OTHER for the two program_states to be
+ considered equal. */
+
+bool
+program_state::operator== (const program_state &other) const
+{
+ if (!(*m_region_model == *other.m_region_model))
+ return false;
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ if (!(*smap == *other.m_checker_states[i]))
+ return false;
+
+ gcc_checking_assert (hash () == other.hash ());
+
+ return true;
+}
+
+/* Print a compact representation of this state to PP. */
+
+void
+program_state::print (const extrinsic_state &ext_state,
+ pretty_printer *pp) const
+{
+ pp_printf (pp, "rmodel: ");
+ m_region_model->print (pp);
+ pp_newline (pp);
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ {
+ if (!smap->is_empty_p ())
+ {
+ pp_printf (pp, "%s: ", ext_state.get_name (i));
+ smap->print (ext_state.get_sm (i), pp);
+ pp_newline (pp);
+ }
+ }
+}
+
+/* Dump a multiline representation of this state to PP. */
+
+void
+program_state::dump_to_pp (const extrinsic_state &ext_state,
+ bool summarize,
+ pretty_printer *pp) const
+{
+ pp_printf (pp, "rmodel: ");
+ m_region_model->dump_to_pp (pp, summarize);
+
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ {
+ if (!smap->is_empty_p ())
+ {
+ pp_printf (pp, "%s: ", ext_state.get_name (i));
+ smap->print (ext_state.get_sm (i), pp);
+ pp_newline (pp);
+ }
+ }
+}
+
+/* Dump a multiline representation of this state to OUTF. */
+
+void
+program_state::dump_to_file (const extrinsic_state &ext_state,
+ bool summarize,
+ FILE *outf) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ if (outf == stderr)
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = outf;
+ dump_to_pp (ext_state, summarize, &pp);
+ pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this state to stderr. */
+
+DEBUG_FUNCTION void
+program_state::dump (const extrinsic_state &ext_state,
+ bool summarize) const
+{
+ dump_to_file (ext_state, summarize, stderr);
+}
+
+/* Determine if following edge SUCC from ENODE is valid within the graph EG
+ and update this state accordingly in-place.
+
+ Return true if the edge can be followed, or false otherwise.
+
+ Check for relevant conditionals and switch-values for conditionals
+ and switch statements, adding the relevant conditions to this state.
+ Push/pop frames for interprocedural edges and update params/returned
+ values.
+
+ This is the "state" half of exploded_node::on_edge. */
+
+bool
+program_state::on_edge (exploded_graph &eg,
+ const exploded_node &enode,
+ const superedge *succ,
+ state_change *change)
+{
+ /* Update state. */
+ const program_point &point = enode.get_point ();
+ const gimple *last_stmt = point.get_supernode ()->get_last_stmt ();
+
+ /* For conditionals and switch statements, add the
+ relevant conditions (for the specific edge) to new_state;
+ skip edges for which the resulting constraints
+ are impossible.
+ This also updates frame information for call/return superedges.
+ Adding the relevant conditions for the edge could also trigger
+ sm-state transitions (e.g. transitions due to ptrs becoming known
+ to be NULL or non-NULL) */
+
+ impl_region_model_context ctxt (eg, &enode,
+ &enode.get_state (),
+ this, change,
+ last_stmt);
+ if (!m_region_model->maybe_update_for_edge (*succ,
+ last_stmt,
+ &ctxt))
+ {
+ logger * const logger = eg.get_logger ();
+ if (logger)
+ logger->log ("edge to SN: %i is impossible"
+ " due to region_model constraints",
+ succ->m_dest->m_index);
+ return false;
+ }
+
+ return true;
+}
+
+/* Generate a simpler version of THIS, discarding state that's no longer
+ relevant at POINT.
+ The idea is that we're more likely to be able to consolidate
+ multiple (point, state) into single exploded_nodes if we discard
+ irrelevant state (e.g. at the end of functions).
+
+ Retain state affected by CHANGE, to make it easier to generate
+ state_change_events. */
+
+program_state
+program_state::prune_for_point (exploded_graph &eg,
+ const program_point &point,
+ state_change *change) const
+{
+ logger * const logger = eg.get_logger ();
+ LOG_SCOPE (logger);
+
+ function *fun = point.get_function ();
+ if (!fun)
+ return *this;
+
+ program_state new_state (*this);
+
+ purge_stats stats;
+
+ const state_purge_map *pm = eg.get_purge_map ();
+ if (pm)
+ {
+ region_id_set purgeable_ssa_regions (new_state.m_region_model);
+ region_id frame_rid
+ = new_state.m_region_model->get_current_frame_id ();
+ frame_region *frame
+ = new_state.m_region_model->get_region <frame_region>(frame_rid);
+
+ /* TODO: maybe move to a member of region_model? */
+
+ auto_vec<tree> ssa_names_to_purge;
+ for (frame_region::map_t::iterator iter = frame->begin ();
+ iter != frame->end ();
+ ++iter)
+ {
+ tree var = (*iter).first;
+ region_id rid = (*iter).second;
+ if (TREE_CODE (var) == SSA_NAME)
+ {
+ const state_purge_per_ssa_name &per_ssa
+ = pm->get_data_for_ssa_name (var);
+ if (!per_ssa.needed_at_point_p (point.get_function_point ()))
+ {
+ region *region
+ = new_state.m_region_model->get_region (rid);
+ svalue_id sid = region->get_value_direct ();
+ if (!sid.null_p ())
+ {
+ if (!new_state.can_purge_p (eg.get_ext_state (), sid))
+ {
+ /* (currently only state maps can keep things
+ alive). */
+ if (logger)
+ logger->log ("not purging RID: %i for %qE"
+ " (used by state map)",
+ rid.as_int (), var);
+ continue;
+ }
+
+ /* Don't purge regions containing svalues that
+ have a change of sm-state, to make it easier to
+ generate state_change_event messages. */
+ if (change)
+ if (change->affects_p (sid))
+ {
+ if (logger)
+ logger->log ("not purging RID: %i for %qE"
+ " (affected by change)",
+ rid.as_int (), var);
+ continue;
+ }
+ }
+ purgeable_ssa_regions.add_region (rid);
+ ssa_names_to_purge.safe_push (var);
+ if (logger)
+ logger->log ("purging RID: %i for %qE", rid.as_int (), var);
+ /* We also need to remove the region from the map.
+ We're in mid-traversal, so the removal is done in
+ unbind below. */
+ }
+ }
+ }
+
+ /* Unbind the regions from the frame's map of vars-to-regions. */
+ unsigned i;
+ tree var;
+ FOR_EACH_VEC_ELT (ssa_names_to_purge, i, var)
+ frame->unbind (var);
+
+ /* Purge the regions. Nothing should point to them, and they
+ should have no children, as they are for SSA names. */
+ new_state.m_region_model->purge_regions (purgeable_ssa_regions,
+ &stats,
+ eg.get_logger ());
+ }
+
+ /* Purge unused svalues. */
+ // TODO: which enode to use, if any?
+ impl_region_model_context ctxt (eg, NULL,
+ this,
+ &new_state,
+ change,
+ NULL);
+ new_state.m_region_model->purge_unused_svalues (&stats, &ctxt);
+ if (logger)
+ {
+ logger->log ("num svalues purged: %i", stats.m_num_svalues);
+ logger->log ("num regions purged: %i", stats.m_num_regions);
+ logger->log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
+ logger->log ("num constraints purged: %i", stats.m_num_constraints);
+ logger->log ("num sm map items purged: %i", stats.m_num_client_items);
+ }
+
+ new_state.m_region_model->canonicalize (&ctxt);
+
+ return new_state;
+}
+
+/* Remap all svalue_ids in this state's m_checker_states according to MAP.
+ The svalues_ids in the region_model are assumed to already have been
+ remapped. */
+
+void
+program_state::remap_svalue_ids (const svalue_id_map &map)
+{
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ smap->remap_svalue_ids (map);
+}
+
+/* Attempt to return a tree that represents SID, or return NULL_TREE.
+ Find the first region that stores the value (e.g. a local) and
+ generate a representative tree for it. */
+
+tree
+program_state::get_representative_tree (svalue_id sid) const
+{
+ return m_region_model->get_representative_tree (sid);
+}
+
+/* Attempt to merge this state with OTHER, both using EXT_STATE.
+ Write the result to *OUT.
+ If the states were merged successfully, return true. */
+
+bool
+program_state::can_merge_with_p (const program_state &other,
+ const extrinsic_state &ext_state,
+ program_state *out) const
+{
+ gcc_assert (out);
+
+ /* TODO: initially I had an early reject here if there
+ are sm-differences between the states. However, this was
+ falsely rejecting merger opportunities for states where the
+ only difference was in svalue_id ordering. */
+
+ /* Attempt to merge the region_models. */
+
+ svalue_id_merger_mapping sid_mapping (*m_region_model,
+ *other.m_region_model);
+ if (!m_region_model->can_merge_with_p (*other.m_region_model,
+ out->m_region_model,
+ &sid_mapping))
+ return false;
+
+ /* Copy m_checker_states to result, remapping svalue_ids using
+ sid_mapping. */
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (out->m_checker_states, i, smap)
+ delete smap;
+ out->m_checker_states.truncate (0);
+
+ /* Remap this and other's m_checker_states using sid_mapping.
+ Only merge states that have equality between the two end-results:
+ sm-state differences are likely to be interesting to end-users, and
+ hence are worth exploring as separate paths in the exploded graph. */
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ {
+ sm_state_map *other_smap = other.m_checker_states[i];
+
+ /* If clone_with_remapping returns NULL for one of the input smaps,
+ then it has sm-state for an svalue_id where the svalue_id is
+ being mapped to svalue_id::null in its sid_mapping, meaning that
+ the svalue is to be dropped during the merger. We don't want
+ to lose sm-state during a state merger, so return false for these
+ cases. */
+ sm_state_map *remapped_a_smap
+ = smap->clone_with_remapping (sid_mapping.m_map_from_a_to_m);
+ if (!remapped_a_smap)
+ return false;
+ sm_state_map *remapped_b_smap
+ = other_smap->clone_with_remapping (sid_mapping.m_map_from_b_to_m);
+ if (!remapped_b_smap)
+ {
+ delete remapped_a_smap;
+ return false;
+ }
+
+ /* Both states have sm-state for the same values; now ensure that the
+ states are equal. */
+ if (*remapped_a_smap == *remapped_b_smap)
+ {
+ out->m_checker_states.safe_push (remapped_a_smap);
+ delete remapped_b_smap;
+ }
+ else
+ {
+ /* Don't merge if there are sm-state differences. */
+ delete remapped_a_smap;
+ delete remapped_b_smap;
+ return false;
+ }
+ }
+
+ impl_region_model_context ctxt (out, NULL, ext_state);
+ out->m_region_model->canonicalize (&ctxt);
+
+ return true;
+}
+
+/* Assert that this object is valid. */
+
+void
+program_state::validate (const extrinsic_state &ext_state) const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+
+ m_region_model->validate ();
+ gcc_assert (m_checker_states.length () == ext_state.get_num_checkers ());
+ int sm_idx;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, sm_idx, smap)
+ {
+ const state_machine &sm = ext_state.get_sm (sm_idx);
+ smap->validate (sm, m_region_model->get_num_svalues ());
+ }
+}
+
+/* Dump this sm_change to PP. */
+
+void
+state_change::sm_change::dump (pretty_printer *pp,
+ const extrinsic_state &ext_state) const
+{
+ const state_machine &sm = get_sm (ext_state);
+ pp_string (pp, "(");
+ m_new_sid.print (pp);
+ pp_printf (pp, ": %s: %qs -> %qs)",
+ sm.get_name (),
+ sm.get_state_name (m_old_state),
+ sm.get_state_name (m_new_state));
+}
+
+/* Remap all svalue_ids in this change according to MAP. */
+
+void
+state_change::sm_change::remap_svalue_ids (const svalue_id_map &map)
+{
+ map.update (&m_new_sid);
+}
+
+/* Purge any svalue_ids >= FIRST_UNUSED_SID.
+ Return the number of states that were purged. */
+
+int
+state_change::sm_change::on_svalue_purge (svalue_id first_unused_sid)
+{
+ if (m_new_sid.as_int () >= first_unused_sid.as_int ())
+ {
+ m_new_sid = svalue_id::null ();
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Assert that this object is sane. */
+
+void
+state_change::sm_change::validate (const program_state &new_state) const
+{
+ m_new_sid.validate (*new_state.m_region_model);
+}
+
+/* state_change's ctor. */
+
+state_change::state_change ()
+{
+}
+
+/* state_change's copy ctor. */
+
+state_change::state_change (const state_change &other)
+: m_sm_changes (other.m_sm_changes.length ())
+{
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (other.m_sm_changes, i, change)
+ m_sm_changes.quick_push (*change);
+}
+
+/* Record a state-machine state change. */
+
+void
+state_change::add_sm_change (int sm_idx,
+ svalue_id new_sid,
+ state_machine::state_t old_state,
+ state_machine::state_t new_state)
+{
+ m_sm_changes.safe_push (sm_change (sm_idx,
+ new_sid,
+ old_state, new_state));
+}
+
+/* Return true if SID (in the new state) was affected by any
+ sm-state changes. */
+
+bool
+state_change::affects_p (svalue_id sid) const
+{
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+ {
+ if (sid == change->m_new_sid)
+ return true;
+ }
+ return false;
+}
+
+/* Dump this state_change to PP. */
+
+void
+state_change::dump (pretty_printer *pp,
+ const extrinsic_state &ext_state) const
+{
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ change->dump (pp, ext_state);
+ }
+}
+
+/* Dump this state_change to stderr. */
+
+void
+state_change::dump (const extrinsic_state &ext_state) const
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ dump (&pp, ext_state);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+/* Remap all svalue_ids in this state_change according to MAP. */
+
+void
+state_change::remap_svalue_ids (const svalue_id_map &map)
+{
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+ change->remap_svalue_ids (map);
+}
+
+/* Purge any svalue_ids >= FIRST_UNUSED_SID.
+ Return the number of states that were purged. */
+
+int
+state_change::on_svalue_purge (svalue_id first_unused_sid)
+{
+ int result = 0;
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+ result += change->on_svalue_purge (first_unused_sid);
+ return result;
+}
+
+/* Assert that this object is sane. */
+
+void
+state_change::validate (const program_state &new_state) const
+{
+ /* Skip this in a release build. */
+#if !CHECKING_P
+ return;
+#endif
+ unsigned i;
+ sm_change *change;
+ FOR_EACH_VEC_ELT (m_sm_changes, i, change)
+ change->validate (new_state);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Tests for sm_state_map. */
+
+static void
+test_sm_state_map ()
+{
+ tree x = build_global_decl ("x", integer_type_node);
+ tree y = build_global_decl ("y", integer_type_node);
+ tree z = build_global_decl ("z", integer_type_node);
+
+ /* Test setting states on svalue_id instances directly. */
+ {
+ region_model model;
+ svalue_id sid_x = model.get_rvalue (x, NULL);
+ svalue_id sid_y = model.get_rvalue (y, NULL);
+ svalue_id sid_z = model.get_rvalue (z, NULL);
+
+ sm_state_map map;
+ ASSERT_TRUE (map.is_empty_p ());
+ ASSERT_EQ (map.get_state (sid_x), 0);
+
+ map.impl_set_state (sid_x, 42, sid_z);
+ ASSERT_EQ (map.get_state (sid_x), 42);
+ ASSERT_EQ (map.get_origin (sid_x), sid_z);
+ ASSERT_EQ (map.get_state (sid_y), 0);
+ ASSERT_FALSE (map.is_empty_p ());
+
+ map.impl_set_state (sid_y, 0, sid_z);
+ ASSERT_EQ (map.get_state (sid_y), 0);
+
+ map.impl_set_state (sid_x, 0, sid_z);
+ ASSERT_EQ (map.get_state (sid_x), 0);
+ ASSERT_TRUE (map.is_empty_p ());
+ }
+
+ /* Test setting states via equivalence classes. */
+ {
+ region_model model;
+ svalue_id sid_x = model.get_rvalue (x, NULL);
+ svalue_id sid_y = model.get_rvalue (y, NULL);
+ svalue_id sid_z = model.get_rvalue (z, NULL);
+
+ sm_state_map map;
+ ASSERT_TRUE (map.is_empty_p ());
+ ASSERT_EQ (map.get_state (sid_x), 0);
+ ASSERT_EQ (map.get_state (sid_y), 0);
+
+ model.add_constraint (x, EQ_EXPR, y, NULL);
+
+ /* Setting x to a state should also update y, as they
+ are in the same equivalence class. */
+ map.set_state (&model, sid_x, 5, sid_z);
+ ASSERT_EQ (map.get_state (sid_x), 5);
+ ASSERT_EQ (map.get_state (sid_y), 5);
+ ASSERT_EQ (map.get_origin (sid_x), sid_z);
+ ASSERT_EQ (map.get_origin (sid_y), sid_z);
+ }
+
+ /* Test equality and hashing. */
+ {
+ region_model model;
+ svalue_id sid_y = model.get_rvalue (y, NULL);
+ svalue_id sid_z = model.get_rvalue (z, NULL);
+
+ sm_state_map map0;
+ sm_state_map map1;
+ sm_state_map map2;
+
+ ASSERT_EQ (map0.hash (), map1.hash ());
+ ASSERT_EQ (map0, map1);
+
+ map1.impl_set_state (sid_y, 5, sid_z);
+ ASSERT_NE (map0.hash (), map1.hash ());
+ ASSERT_NE (map0, map1);
+
+ /* Make the same change to map2. */
+ map2.impl_set_state (sid_y, 5, sid_z);
+ ASSERT_EQ (map1.hash (), map2.hash ());
+ ASSERT_EQ (map1, map2);
+ }
+
+ /* Equality and hashing shouldn't depend on ordering. */
+ {
+ sm_state_map map0;
+ sm_state_map map1;
+ sm_state_map map2;
+
+ ASSERT_EQ (map0.hash (), map1.hash ());
+ ASSERT_EQ (map0, map1);
+
+ map1.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
+ map1.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
+ map1.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
+ map1.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
+
+ map2.impl_set_state (svalue_id::from_int (1), 2, svalue_id::null ());
+ map2.impl_set_state (svalue_id::from_int (16), 3, svalue_id::null ());
+ map2.impl_set_state (svalue_id::from_int (14), 2, svalue_id::null ());
+ map2.impl_set_state (svalue_id::from_int (9), 2, svalue_id::null ());
+
+ ASSERT_EQ (map1.hash (), map2.hash ());
+ ASSERT_EQ (map1, map2);
+ }
+
+ /* Test sm_state_map::remap_svalue_ids. */
+ {
+ sm_state_map map;
+ svalue_id sid_0 = svalue_id::from_int (0);
+ svalue_id sid_1 = svalue_id::from_int (1);
+ svalue_id sid_2 = svalue_id::from_int (2);
+
+ map.impl_set_state (sid_0, 42, sid_2);
+ ASSERT_EQ (map.get_state (sid_0), 42);
+ ASSERT_EQ (map.get_origin (sid_0), sid_2);
+ ASSERT_EQ (map.get_state (sid_1), 0);
+ ASSERT_EQ (map.get_state (sid_2), 0);
+
+ /* Apply a remapping to the IDs. */
+ svalue_id_map remapping (3);
+ remapping.put (sid_0, sid_1);
+ remapping.put (sid_1, sid_2);
+ remapping.put (sid_2, sid_0);
+ map.remap_svalue_ids (remapping);
+
+ /* Verify that the IDs have been remapped. */
+ ASSERT_EQ (map.get_state (sid_1), 42);
+ ASSERT_EQ (map.get_origin (sid_1), sid_0);
+ ASSERT_EQ (map.get_state (sid_2), 0);
+ ASSERT_EQ (map.get_state (sid_0), 0);
+ }
+
+ // TODO: coverage for purging
+}
+
+/* Verify that program_states with identical sm-state can be merged,
+ and that the merged program_state preserves the sm-state. */
+
+static void
+test_program_state_merging ()
+{
+ /* Create a program_state for a global ptr "p" that has
+ malloc sm-state, pointing to a region on the heap. */
+ tree p = build_global_decl ("p", ptr_type_node);
+
+ auto_delete_vec <state_machine> checkers;
+ checkers.safe_push (make_malloc_state_machine (NULL));
+ extrinsic_state ext_state (checkers);
+
+ program_state s0 (ext_state);
+ impl_region_model_context ctxt (&s0, NULL, ext_state);
+
+ region_model *model0 = s0.m_region_model;
+ region_id new_rid = model0->add_new_malloc_region ();
+ svalue_id ptr_sid
+ = model0->get_or_create_ptr_svalue (ptr_type_node, new_rid);
+ model0->set_value (model0->get_lvalue (p, &ctxt),
+ ptr_sid, &ctxt);
+ sm_state_map *smap = s0.m_checker_states[0];
+ const state_machine::state_t TEST_STATE = 3;
+ smap->impl_set_state (ptr_sid, TEST_STATE, svalue_id::null ());
+ ASSERT_EQ (smap->get_state (ptr_sid), TEST_STATE);
+
+ model0->canonicalize (&ctxt);
+
+ /* Verify that canonicalization preserves sm-state. */
+ ASSERT_EQ (smap->get_state (model0->get_rvalue (p, NULL)), TEST_STATE);
+
+ /* Make a copy of the program_state. */
+ program_state s1 (s0);
+ ASSERT_EQ (s0, s1);
+
+ /* We have two identical states with "p" pointing to a heap region
+ with the given sm-state.
+ They ought to be mergeable, preserving the sm-state. */
+ program_state merged (ext_state);
+ ASSERT_TRUE (s0.can_merge_with_p (s1, ext_state, &merged));
+ merged.validate (ext_state);
+
+ /* Verify that the merged state has the sm-state for "p". */
+ region_model *merged_model = merged.m_region_model;
+ sm_state_map *merged_smap = merged.m_checker_states[0];
+ ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+ TEST_STATE);
+
+ /* Try canonicalizing. */
+ impl_region_model_context merged_ctxt (&merged, NULL, ext_state);
+ merged.m_region_model->canonicalize (&merged_ctxt);
+ merged.validate (ext_state);
+
+ /* Verify that the merged state still has the sm-state for "p". */
+ ASSERT_EQ (merged_smap->get_state (merged_model->get_rvalue (p, NULL)),
+ TEST_STATE);
+
+ /* After canonicalization, we ought to have equality with the inputs. */
+ ASSERT_EQ (s0, merged);
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_program_state_cc_tests ()
+{
+ test_sm_state_map ();
+ test_program_state_merging ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h
new file mode 100644
index 00000000000..75b65b780c9
--- /dev/null
+++ b/gcc/analyzer/program-state.h
@@ -0,0 +1,363 @@
+/* Classes for representing the state of interest at a given path of analysis.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_ANALYZER_PROGRAM_STATE_H
+#define GCC_ANALYZER_PROGRAM_STATE_H
+
+/* Data shared by all program_state instances. */
+
+class extrinsic_state
+{
+public:
+ extrinsic_state (auto_delete_vec <state_machine> &checkers)
+ : m_checkers (checkers)
+ {
+ }
+
+ const state_machine &get_sm (int idx) const
+ {
+ return *m_checkers[idx];
+ }
+
+ const char *get_name (int idx) const
+ {
+ return m_checkers[idx]->get_name ();
+ }
+
+ unsigned get_num_checkers () const { return m_checkers.length (); }
+
+ /* The state machines. */
+ auto_delete_vec <state_machine> &m_checkers;
+};
+
+template <> struct default_hash_traits<svalue_id>
+: public pod_hash_traits<svalue_id>
+{
+ static const bool empty_zero_p = false;
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<svalue_id>::hash (value_type v)
+{
+ return v.as_int ();
+}
+
+template <>
+inline bool
+pod_hash_traits<svalue_id>::equal (const value_type &existing,
+ const value_type &candidate)
+{
+ return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_deleted (value_type &v)
+{
+ v = svalue_id::from_int (-2);
+}
+template <>
+inline void
+pod_hash_traits<svalue_id>::mark_empty (value_type &v)
+{
+ v = svalue_id::null ();
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_deleted (value_type v)
+{
+ return v.as_int () == -2;
+}
+template <>
+inline bool
+pod_hash_traits<svalue_id>::is_empty (value_type v)
+{
+ return v.null_p ();
+}
+
+/* Map from svalue_id to state machine state, also capturing the origin of
+ each state. */
+
+class sm_state_map
+{
+public:
+ /* An entry in the hash_map. */
+ struct entry_t
+ {
+ /* Default ctor needed by hash_map::empty. */
+ entry_t ()
+ : m_state (0), m_origin (svalue_id::null ())
+ {
+ }
+
+ entry_t (state_machine::state_t state,
+ svalue_id origin)
+ : m_state (state), m_origin (origin)
+ {}
+
+ bool operator== (const entry_t &other) const
+ {
+ return (m_state == other.m_state
+ && m_origin == other.m_origin);
+ }
+ bool operator!= (const entry_t &other) const
+ {
+ return !(*this == other);
+ }
+
+ state_machine::state_t m_state;
+ svalue_id m_origin;
+ };
+ typedef hash_map <svalue_id, entry_t> map_t;
+ typedef typename map_t::iterator iterator_t;
+
+ sm_state_map ();
+
+ sm_state_map *clone () const;
+
+ sm_state_map *
+ clone_with_remapping (const one_way_svalue_id_map &id_map) const;
+
+ void print (const state_machine &sm, pretty_printer *pp) const;
+ void dump (const state_machine &sm) const;
+
+ bool is_empty_p () const;
+
+ hashval_t hash () const;
+
+ bool operator== (const sm_state_map &other) const;
+ bool operator!= (const sm_state_map &other) const
+ {
+ return !(*this == other);
+ }
+
+ state_machine::state_t get_state (svalue_id sid) const;
+ svalue_id get_origin (svalue_id sid) const;
+
+ void set_state (region_model *model,
+ svalue_id sid,
+ state_machine::state_t state,
+ svalue_id origin);
+ void set_state (const equiv_class &ec,
+ state_machine::state_t state,
+ svalue_id origin);
+ void impl_set_state (svalue_id sid,
+ state_machine::state_t state,
+ svalue_id origin);
+
+ void set_global_state (state_machine::state_t state);
+ state_machine::state_t get_global_state () const;
+
+ void purge_for_unknown_fncall (const exploded_graph &eg,
+ const state_machine &sm,
+ const gcall *call, tree fndecl,
+ region_model *new_model);
+
+ void remap_svalue_ids (const svalue_id_map &map);
+
+ int on_svalue_purge (const state_machine &sm,
+ int sm_idx,
+ svalue_id first_unused_sid,
+ const svalue_id_map &map,
+ impl_region_model_context *ctxt);
+
+ void on_inherited_svalue (svalue_id parent_sid,
+ svalue_id child_sid);
+
+ void on_cast (svalue_id src_sid,
+ svalue_id dst_sid);
+
+ void validate (const state_machine &sm, int num_svalues) const;
+
+ iterator_t begin () const { return m_map.begin (); }
+ iterator_t end () const { return m_map.end (); }
+
+private:
+ map_t m_map;
+ state_machine::state_t m_global_state;
+};
+
+/* A class for representing the state of interest at a given path of
+ analysis.
+
+ Currently this is a combination of:
+ (a) a region_model, giving:
+ (a.1) a hierarchy of memory regions
+ (a.2) values for the regions
+ (a.3) inequalities between values
+ (b) sm_state_maps per state machine, giving a sparse mapping of
+ values to states. */
+
+class program_state
+{
+public:
+ program_state (const extrinsic_state &ext_state);
+ program_state (const program_state &other);
+ program_state& operator= (const program_state &other);
+
+#if __cplusplus >= 201103
+ program_state (program_state &&other);
+ program_state& operator= (program_state &&other); // doesn't seem to be used
+#endif
+
+ ~program_state ();
+
+ hashval_t hash () const;
+ bool operator== (const program_state &other) const;
+ bool operator!= (const program_state &other) const
+ {
+ return !(*this == other);
+ }
+
+ void print (const extrinsic_state &ext_state,
+ pretty_printer *pp) const;
+
+ void dump_to_pp (const extrinsic_state &ext_state, bool summarize,
+ pretty_printer *pp) const;
+ void dump_to_file (const extrinsic_state &ext_state, bool summarize,
+ FILE *outf) const;
+ void dump (const extrinsic_state &ext_state, bool summarize) const;
+
+ bool on_edge (exploded_graph &eg,
+ const exploded_node &enode,
+ const superedge *succ,
+ state_change *change);
+
+ program_state prune_for_point (exploded_graph &eg,
+ const program_point &point,
+ state_change *change) const;
+
+ void remap_svalue_ids (const svalue_id_map &map);
+
+ tree get_representative_tree (svalue_id sid) const;
+
+ bool can_purge_p (const extrinsic_state &ext_state,
+ svalue_id sid)
+ {
+ /* Don't purge vars that have non-purgeable sm state, to avoid
+ generating false "leak" complaints. */
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+ {
+ const state_machine &sm = ext_state.get_sm (i);
+ if (!sm.can_purge_p (smap->get_state (sid)))
+ return false;
+ }
+ return true;
+ }
+
+ bool can_merge_with_p (const program_state &other,
+ const extrinsic_state &ext_state,
+ program_state *out) const;
+
+ void validate (const extrinsic_state &ext_state) const;
+
+ /* TODO: lose the pointer here (const-correctness issues?). */
+ region_model *m_region_model;
+ auto_delete_vec<sm_state_map> m_checker_states;
+};
+
+/* An abstract base class for use with for_each_state_change. */
+
+class state_change_visitor
+{
+public:
+ virtual ~state_change_visitor () {}
+
+ /* Return true for early exit, false to keep iterating. */
+ virtual bool on_global_state_change (const state_machine &sm,
+ state_machine::state_t src_sm_val,
+ state_machine::state_t dst_sm_val) = 0;
+
+ /* Return true for early exit, false to keep iterating. */
+ virtual bool on_state_change (const state_machine &sm,
+ state_machine::state_t src_sm_val,
+ state_machine::state_t dst_sm_val,
+ tree dst_rep,
+ svalue_id dst_origin_sid) = 0;
+};
+
+extern bool for_each_state_change (const program_state &src_state,
+ const program_state &dst_state,
+ const extrinsic_state &ext_state,
+ state_change_visitor *visitor);
+
+/* A class for recording "interesting" state changes.
+ This is used for annotating edges in the GraphViz output of the
+ exploded_graph, and for recording sm-state-changes, so that
+ values that change aren't purged (to make it easier to generate
+ state_change_event instances in the diagnostic_path). */
+
+class state_change
+{
+ public:
+ struct sm_change
+ {
+ sm_change (int sm_idx,
+ svalue_id new_sid,
+ state_machine::state_t old_state,
+ state_machine::state_t new_state)
+ : m_sm_idx (sm_idx),
+ m_new_sid (new_sid),
+ m_old_state (old_state), m_new_state (new_state)
+ {}
+
+ const state_machine &get_sm (const extrinsic_state &ext_state) const
+ {
+ return ext_state.get_sm (m_sm_idx);
+ }
+
+ void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
+
+ void remap_svalue_ids (const svalue_id_map &map);
+ int on_svalue_purge (svalue_id first_unused_sid);
+
+ void validate (const program_state &new_state) const;
+
+ int m_sm_idx;
+ svalue_id m_new_sid;
+ state_machine::state_t m_old_state;
+ state_machine::state_t m_new_state;
+ };
+
+ state_change ();
+ state_change (const state_change &other);
+
+ void add_sm_change (int sm_idx,
+ svalue_id new_sid,
+ state_machine::state_t old_state,
+ state_machine::state_t new_state);
+
+ bool affects_p (svalue_id sid) const;
+
+ void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
+ void dump (const extrinsic_state &ext_state) const;
+
+ void remap_svalue_ids (const svalue_id_map &map);
+ int on_svalue_purge (svalue_id first_unused_sid);
+
+ void validate (const program_state &new_state) const;
+
+ private:
+ auto_vec<sm_change> m_sm_changes;
+};
+
+#endif /* GCC_ANALYZER_PROGRAM_STATE_H */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
new file mode 100644
index 00000000000..2b0945a4ce8
--- /dev/null
+++ b/gcc/analyzer/region-model.cc
@@ -0,0 +1,7785 @@
+/* Classes for modeling the state of memory.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "graphviz.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "stringpool.h"
+#include "convert.h"
+#include "target.h"
+#include "fold-const.h"
+#include "tree-pretty-print.h"
+#include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-core.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/analyzer-selftests.h"
+
+#if ENABLE_ANALYZER
+
+/* Dump T to PP in language-independent form, for debugging/logging/dumping
+ purposes. */
+
+static void
+dump_tree (pretty_printer *pp, tree t)
+{
+ dump_generic_node (pp, t, 0, TDF_SLIM, 0);
+}
+
+/* Dump this path_var to PP (which must support %E for trees).
+
+ Express the stack depth using an "@DEPTH" suffix, so e.g. given
+ void foo (int j);
+ void bar (int i)
+ {
+ foo (i);
+ }
+ then:
+ - the "i" in "bar" would be "(i @ 0)"
+ - the "j" in "foo" would be "(j @ 1)". */
+
+void
+path_var::dump (pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+ if (m_tree == NULL_TREE)
+ pp_string (pp, "NULL");
+ if (CONSTANT_CLASS_P (m_tree))
+ pp_printf (pp, "%qE", m_tree);
+ else
+ pp_printf (pp, "(%qE @ %i)", m_tree, m_stack_depth);
+POP_IGNORE_WFORMAT
+}
+
+/* For use in printing a comma-separated list. */
+
+static void
+dump_separator (pretty_printer *pp, bool *is_first)
+{
+ if (!*is_first)
+ pp_string (pp, ", ");
+ *is_first = false;
+}
+
+/* Concrete subclass of constraint_manager that wires it up to a region_model
+ (whilst allowing the constraint_manager and region_model to be somewhat
+ at arms length).
+ TODO: revisit this; maybe put the region_model * into the constraint_manager
+ base class. */
+
+class impl_constraint_manager : public constraint_manager
+{
+ public:
+ impl_constraint_manager (region_model *model)
+ : constraint_manager (),
+ m_model (model)
+ {}
+
+ impl_constraint_manager (const impl_constraint_manager &other,
+ region_model *model)
+ : constraint_manager (other),
+ m_model (model)
+ {}
+
+ constraint_manager *clone (region_model *model) const
+ {
+ return new impl_constraint_manager (*this, model);
+ }
+
+ tree maybe_get_constant (svalue_id sid) const FINAL OVERRIDE
+ {
+ svalue *svalue = m_model->get_svalue (sid);
+ return svalue->maybe_get_constant ();
+ }
+
+ svalue_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+ {
+ gcc_assert (CONSTANT_CLASS_P (cst));
+ return m_model->get_rvalue (cst, NULL);
+ }
+
+ int get_num_svalues () const FINAL OVERRIDE
+ {
+ return m_model->get_num_svalues ();
+ }
+
+ private:
+ region_model *m_model;
+};
+
+/* class svalue_id. */
+
+/* Print this svalue_id to PP. */
+
+void
+svalue_id::print (pretty_printer *pp) const
+{
+ if (null_p ())
+ pp_printf (pp, "null");
+ else
+ pp_printf (pp, "sv%i", m_idx);
+}
+
+/* Print this svalue_id in .dot format to PP. */
+
+void
+svalue_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+ gcc_assert (!null_p ());
+ pp_printf (pp, "svalue_%i", m_idx);
+}
+
+/* Assert that this object is valid (w.r.t. MODEL). */
+
+void
+svalue_id::validate (const region_model &model) const
+{
+ gcc_assert (null_p () || m_idx < (int)model.get_num_svalues ());
+}
+
+/* class region_id. */
+
+/* Print this region_id to PP. */
+
+void
+region_id::print (pretty_printer *pp) const
+{
+ if (null_p ())
+ pp_printf (pp, "null");
+ else
+ pp_printf (pp, "r%i", m_idx);
+}
+
+/* Print this region_id in .dot format to PP. */
+
+void
+region_id::dump_node_name_to_pp (pretty_printer *pp) const
+{
+ gcc_assert (!null_p ());
+ pp_printf (pp, "region_%i", m_idx);
+}
+
+/* Assert that this object is valid (w.r.t. MODEL). */
+
+void
+region_id::validate (const region_model &model) const
+{
+ gcc_assert (null_p () || m_idx < (int)model.get_num_regions ());
+}
+
+/* class id_set. */
+
+/* id_set<region_id>'s ctor. */
+
+template<>
+id_set<region_id>::id_set (const region_model *model)
+: m_bitmap (model->get_num_regions ())
+{
+ bitmap_clear (m_bitmap);
+}
+
+/* class svalue and its various subclasses. */
+
+/* class svalue. */
+
+/* svalue's equality operator. Most of the work is done by the
+ a "compare_fields" implementation on each subclass. */
+
+bool
+svalue::operator== (const svalue &other) const
+{
+ enum svalue_kind this_kind = get_kind ();
+ enum svalue_kind other_kind = other.get_kind ();
+ if (this_kind != other_kind)
+ return false;
+
+ if (m_type != other.m_type)
+ return false;
+
+ switch (this_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case SK_REGION:
+ {
+ const region_svalue &this_sub
+ = (const region_svalue &)*this;
+ const region_svalue &other_sub
+ = (const region_svalue &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ break;
+ case SK_CONSTANT:
+ {
+ const constant_svalue &this_sub
+ = (const constant_svalue &)*this;
+ const constant_svalue &other_sub
+ = (const constant_svalue &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ break;
+ case SK_UNKNOWN:
+ {
+ const unknown_svalue &this_sub
+ = (const unknown_svalue &)*this;
+ const unknown_svalue &other_sub
+ = (const unknown_svalue &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ break;
+ case SK_POISONED:
+ {
+ const poisoned_svalue &this_sub
+ = (const poisoned_svalue &)*this;
+ const poisoned_svalue &other_sub
+ = (const poisoned_svalue &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ break;
+ case SK_SETJMP:
+ {
+ const setjmp_svalue &this_sub
+ = (const setjmp_svalue &)*this;
+ const setjmp_svalue &other_sub
+ = (const setjmp_svalue &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ break;
+ }
+}
+
+/* Generate a hash value for this svalue. Most of the work is done by the
+ add_to_hash vfunc. */
+
+hashval_t
+svalue::hash () const
+{
+ inchash::hash hstate;
+ if (m_type)
+ hstate.add_int (TYPE_UID (m_type));
+ add_to_hash (hstate);
+ return hstate.end ();
+}
+
+/* Print this svalue and its ID to PP. */
+
+void
+svalue::print (const region_model &model,
+ svalue_id this_sid,
+ pretty_printer *pp) const
+{
+ this_sid.print (pp);
+ pp_string (pp, ": {");
+
+PUSH_IGNORE_WFORMAT
+ if (m_type)
+ {
+ gcc_assert (TYPE_P (m_type));
+ pp_printf (pp, "type: %qT, ", m_type);
+ }
+POP_IGNORE_WFORMAT
+
+ /* vfunc. */
+ print_details (model, this_sid, pp);
+
+ pp_string (pp, "}");
+}
+
+/* Dump this svalue in the form of a .dot record to PP. */
+
+void
+svalue::dump_dot_to_pp (const region_model &model,
+ svalue_id this_sid,
+ pretty_printer *pp) const
+{
+ this_sid.dump_node_name_to_pp (pp);
+ pp_printf (pp, " [label=\"");
+ pp_write_text_to_stream (pp);
+ this_sid.print (pp);
+ pp_string (pp, ": {");
+ print (model, this_sid, pp);
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+ pp_string (pp, "}\"];");
+ pp_newline (pp);
+}
+
+/* Base implementation of svalue::remap_region_ids vfunc. */
+
+void
+svalue::remap_region_ids (const region_id_map &)
+{
+ /* Empty. */
+}
+
+/* Base implementation of svalue::walk_for_canonicalization vfunc. */
+
+void
+svalue::walk_for_canonicalization (canonicalization *) const
+{
+ /* Empty. */
+}
+
+/* Base implementation of svalue::get_child_sid vfunc. */
+
+svalue_id
+svalue::get_child_sid (region *parent ATTRIBUTE_UNUSED,
+ region *child,
+ region_model &model,
+ region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+ svalue *new_child_value = clone ();
+ if (child->get_type ())
+ new_child_value->m_type = child->get_type ();
+ svalue_id new_child_sid = model.add_svalue (new_child_value);
+ return new_child_sid;
+}
+
+/* If this svalue is a constant_svalue, return the underlying tree constant.
+ Otherwise return NULL_TREE. */
+
+tree
+svalue::maybe_get_constant () const
+{
+ if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ())
+ return cst_sval->get_constant ();
+ else
+ return NULL_TREE;
+}
+
+/* class region_svalue : public svalue. */
+
+/* Compare the fields of this region_svalue with OTHER, returning true
+ if they are equal.
+ For use by svalue::operator==. */
+
+bool
+region_svalue::compare_fields (const region_svalue &other) const
+{
+ return m_rid == other.m_rid;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for region_svalue. */
+
+void
+region_svalue::add_to_hash (inchash::hash &hstate) const
+{
+ inchash::add (m_rid, hstate);
+}
+
+/* Implementation of svalue::print_details vfunc for region_svalue. */
+
+void
+region_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+ svalue_id this_sid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+ if (m_rid.null_p ())
+ pp_string (pp, "NULL");
+ else
+ {
+ pp_string (pp, "&");
+ m_rid.print (pp);
+ }
+}
+
+/* Implementation of svalue::dump_dot_to_pp for region_svalue. */
+
+void
+region_svalue::dump_dot_to_pp (const region_model &model,
+ svalue_id this_sid,
+ pretty_printer *pp) const
+{
+ svalue::dump_dot_to_pp (model, this_sid, pp);
+
+ /* If non-NULL, add an edge to the pointed-to region. */
+ if (!m_rid.null_p ())
+ {
+ this_sid.dump_node_name_to_pp (pp);
+ pp_string (pp, " -> ");
+ m_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, ";");
+ pp_newline (pp);
+ }
+}
+
+/* Implementation of svalue::remap_region_ids vfunc for region_svalue. */
+
+void
+region_svalue::remap_region_ids (const region_id_map &map)
+{
+ map.update (&m_rid);
+}
+
+/* Merge REGION_SVAL_A and REGION_SVAL_B using MERGER, writing the result
+ into *MERGED_SID. */
+
+void
+region_svalue::merge_values (const region_svalue ®ion_sval_a,
+ const region_svalue ®ion_sval_b,
+ svalue_id *merged_sid,
+ tree type,
+ model_merger *merger)
+{
+ region_id a_rid = region_sval_a.get_pointee ();
+ region_id b_rid = region_sval_b.get_pointee ();
+
+ /* Both are non-NULL. */
+ gcc_assert (!a_rid.null_p () && !b_rid.null_p ());
+
+ /* Have these ptr-values already been merged? */
+
+ region_id a_rid_in_m
+ = merger->m_map_regions_from_a_to_m.get_dst_for_src (a_rid);
+ region_id b_rid_in_m
+ = merger->m_map_regions_from_b_to_m.get_dst_for_src (b_rid);
+
+ /* "null_p" here means "we haven't seen this ptr-value before".
+ If we've seen one but not the other, or we have different
+ regions, then the merged ptr has to be "unknown". */
+ if (a_rid_in_m != b_rid_in_m)
+ {
+ svalue *merged_sval = new unknown_svalue (type);
+ *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+ return;
+ }
+
+ /* Have we seen this yet? If so, reuse the value. */
+ if (!a_rid_in_m.null_p ())
+ {
+ *merged_sid
+ = merger->m_merged_model->get_or_create_ptr_svalue (type, a_rid_in_m);
+ return;
+ }
+
+ /* Otherwise we have A/B regions that haven't been referenced yet. */
+
+ /* Are the regions the "same", when seen from the tree point-of-view.
+ If so, create a merged pointer to it. */
+ path_var pv_a = merger->m_model_a->get_representative_path_var (a_rid);
+ path_var pv_b = merger->m_model_b->get_representative_path_var (b_rid);
+ if (pv_a.m_tree
+ && pv_a == pv_b)
+ {
+ region_id merged_pointee_rid
+ = merger->m_merged_model->get_lvalue (pv_a, NULL);
+ *merged_sid
+ = merger->m_merged_model->get_or_create_ptr_svalue (type,
+ merged_pointee_rid);
+ merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+ return;
+ }
+
+ /* Handle an A/B pair of ptrs that both point at heap regions.
+ If they both have a heap region in the merger model, merge them. */
+ region *region_a = merger->m_model_a->get_region (a_rid);
+ region *region_b = merger->m_model_b->get_region (b_rid);
+ region_id a_parent_rid = region_a->get_parent ();
+ region_id b_parent_rid = region_b->get_parent ();
+ region *parent_region_a = merger->m_model_a->get_region (a_parent_rid);
+ region *parent_region_b = merger->m_model_b->get_region (b_parent_rid);
+ if (parent_region_a
+ && parent_region_b
+ && parent_region_a->get_kind () == RK_HEAP
+ && parent_region_b->get_kind () == RK_HEAP)
+ {
+ /* We have an A/B pair of ptrs that both point at heap regions. */
+ /* presumably we want to see if each A/B heap region already
+ has a merged region, and, if so, is it the same one. */
+ // This check is above
+
+ region_id merged_pointee_rid
+ = merger->m_merged_model->add_new_malloc_region ();
+ *merged_sid
+ = merger->m_merged_model->get_or_create_ptr_svalue
+ (type, merged_pointee_rid);
+ merger->record_regions (a_rid, b_rid, merged_pointee_rid);
+ return;
+ }
+
+ /* Two different non-NULL pointers? Merge to unknown. */
+ svalue *merged_sval = new unknown_svalue (type);
+ *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+ return;
+}
+
+/* Implementation of svalue::walk_for_canonicalization vfunc for
+ region_svalue. */
+
+void
+region_svalue::walk_for_canonicalization (canonicalization *c) const
+{
+ c->walk_rid (m_rid);
+}
+
+/* Evaluate the condition LHS OP RHS.
+ Subroutine of region_model::eval_condition for when we have a pair of
+ pointers. */
+
+tristate
+region_svalue::eval_condition (region_svalue *lhs,
+ enum tree_code op,
+ region_svalue *rhs)
+{
+ /* See if they point to the same region. */
+ /* TODO: what about child regions where the child is the first child
+ (or descendent)? */
+ region_id lhs_rid = lhs->get_pointee ();
+ region_id rhs_rid = rhs->get_pointee ();
+ switch (op)
+ {
+ default:
+ gcc_unreachable ();
+
+ case EQ_EXPR:
+ if (lhs_rid == rhs_rid)
+ return tristate::TS_TRUE;
+ else
+ return tristate::TS_FALSE;
+ break;
+
+ case NE_EXPR:
+ if (lhs_rid != rhs_rid)
+ return tristate::TS_TRUE;
+ else
+ return tristate::TS_FALSE;
+ break;
+
+ case GE_EXPR:
+ case LE_EXPR:
+ if (lhs_rid == rhs_rid)
+ return tristate::TS_TRUE;
+ break;
+
+ case GT_EXPR:
+ case LT_EXPR:
+ if (lhs_rid == rhs_rid)
+ return tristate::TS_FALSE;
+ break;
+ }
+
+ return tristate::TS_UNKNOWN;
+}
+
+/* class constant_svalue : public svalue. */
+
+/* Compare the fields of this constant_svalue with OTHER, returning true
+ if they are equal.
+ For use by svalue::operator==. */
+
+bool
+constant_svalue::compare_fields (const constant_svalue &other) const
+{
+ return m_cst_expr == other.m_cst_expr;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for constant_svalue. */
+
+void
+constant_svalue::add_to_hash (inchash::hash &hstate) const
+{
+ inchash::add_expr (m_cst_expr, hstate);
+}
+
+/* Merge the CST_SVAL_A and CST_SVAL_B using MERGER, writing the id of
+ the resulting svalue into *MERGED_SID. */
+
+void
+constant_svalue::merge_values (const constant_svalue &cst_sval_a,
+ const constant_svalue &cst_sval_b,
+ svalue_id *merged_sid,
+ model_merger *merger)
+{
+ tree cst_a = cst_sval_a.get_constant ();
+ tree cst_b = cst_sval_b.get_constant ();
+ svalue *merged_sval;
+ if (cst_a == cst_b)
+ {
+ /* If they are the same constant, merge as that constant value. */
+ merged_sval = new constant_svalue (cst_a);
+ }
+ else
+ {
+ /* Otherwise, we have two different constant values.
+ Merge as an unknown value.
+ TODO: impose constraints on the value?
+ (maybe just based on A, to avoid infinite chains) */
+ merged_sval = new unknown_svalue (TREE_TYPE (cst_a));
+ }
+ *merged_sid = merger->m_merged_model->add_svalue (merged_sval);
+}
+
+/* Evaluate the condition LHS OP RHS.
+ Subroutine of region_model::eval_condition for when we have a pair of
+ constants. */
+
+tristate
+constant_svalue::eval_condition (constant_svalue *lhs,
+ enum tree_code op,
+ constant_svalue *rhs)
+{
+ tree lhs_const = lhs->get_constant ();
+ tree rhs_const = rhs->get_constant ();
+
+ gcc_assert (CONSTANT_CLASS_P (lhs_const));
+ gcc_assert (CONSTANT_CLASS_P (rhs_const));
+
+ tree comparison
+ = fold_build2 (op, boolean_type_node, lhs_const, rhs_const);
+ if (comparison == boolean_true_node)
+ return tristate (tristate::TS_TRUE);
+ if (comparison == boolean_false_node)
+ return tristate (tristate::TS_FALSE);
+ return tristate::TS_UNKNOWN;
+}
+
+/* Implementation of svalue::print_details vfunc for constant_svalue. */
+
+void
+constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+ svalue_id this_sid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qE", m_cst_expr);
+POP_IGNORE_WFORMAT
+}
+
+/* Implementation of svalue::get_child_sid vfunc for constant_svalue. */
+
+svalue_id
+constant_svalue::get_child_sid (region *parent ATTRIBUTE_UNUSED,
+ region *child,
+ region_model &model,
+ region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+ /* TODO: handle the all-zeroes case by returning an all-zeroes of the
+ child type. */
+
+ /* Otherwise, we don't have a good way to get a child value out of a
+ constant.
+
+ Handle this case by using an unknown value. */
+ svalue *unknown_sval = new unknown_svalue (child->get_type ());
+ return model.add_svalue (unknown_sval);
+}
+
+/* class unknown_svalue : public svalue. */
+
+/* Compare the fields of this unknown_svalue with OTHER, returning true
+ if they are equal.
+ For use by svalue::operator==. */
+
+bool
+unknown_svalue::compare_fields (const unknown_svalue &) const
+{
+ /* I *think* we want to return true here, in that when comparing
+ two region models, we want two peer unknown_svalue instances
+ to be the "same". */
+ return true;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for unknown_svalue. */
+
+void
+unknown_svalue::add_to_hash (inchash::hash &) const
+{
+ /* Empty. */
+}
+
+/* Implementation of svalue::print_details vfunc for unknown_svalue. */
+
+void
+unknown_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+ svalue_id this_sid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+ pp_string (pp, "unknown");
+}
+
+/* Get a string for KIND for use in debug dumps. */
+
+const char *
+poison_kind_to_str (enum poison_kind kind)
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case POISON_KIND_UNINIT:
+ return "uninit";
+ case POISON_KIND_FREED:
+ return "freed";
+ case POISON_KIND_POPPED_STACK:
+ return "popped stack";
+ }
+}
+
+/* class poisoned_svalue : public svalue. */
+
+/* Compare the fields of this poisoned_svalue with OTHER, returning true
+ if they are equal.
+ For use by svalue::operator==. */
+
+bool
+poisoned_svalue::compare_fields (const poisoned_svalue &other) const
+{
+ return m_kind == other.m_kind;
+}
+
+/* Implementation of svalue::add_to_hash vfunc for poisoned_svalue. */
+
+void
+poisoned_svalue::add_to_hash (inchash::hash &hstate) const
+{
+ hstate.add_int (m_kind);
+}
+
+/* Implementation of svalue::print_details vfunc for poisoned_svalue. */
+
+void
+poisoned_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
+ svalue_id this_sid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+ pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
+}
+
+/* class setjmp_svalue's implementation is in engine.cc, so that it can use
+ the declaration of exploded_node. */
+
+/* class region and its various subclasses. */
+
+/* Get a string for KIND for use in debug dumps. */
+
+const char *
+region_kind_to_str (enum region_kind kind)
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case RK_PRIMITIVE:
+ return "primitive";
+ case RK_STRUCT:
+ return "struct";
+ case RK_UNION:
+ return "union";
+ case RK_ARRAY:
+ return "array";
+ case RK_FRAME:
+ return "frame";
+ case RK_GLOBALS:
+ return "globals";
+ case RK_CODE:
+ return "code";
+ case RK_FUNCTION:
+ return "function";
+ case RK_STACK:
+ return "stack";
+ case RK_HEAP:
+ return "heap";
+ case RK_ROOT:
+ return "root";
+ case RK_SYMBOLIC:
+ return "symbolic";
+ }
+}
+
+/* class region. */
+
+/* Equality operator for region.
+ After comparing base class fields and kind, the rest of the
+ comparison is handled off to a "compare_fields" member function
+ specific to the appropriate subclass. */
+
+bool
+region::operator== (const region &other) const
+{
+ if (m_parent_rid != other.m_parent_rid)
+ return false;
+ if (m_sval_id != other.m_sval_id)
+ return false;
+ if (m_type != other.m_type)
+ return false;
+
+ enum region_kind this_kind = get_kind ();
+ enum region_kind other_kind = other.get_kind ();
+ if (this_kind != other_kind)
+ return false;
+
+ /* Compare views. */
+ if (m_view_rids.length () != other.m_view_rids.length ())
+ return false;
+ int i;
+ region_id *rid;
+ FOR_EACH_VEC_ELT (m_view_rids, i, rid)
+ if (! (*rid == other.m_view_rids[i]))
+ return false;
+
+ switch (this_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case RK_PRIMITIVE:
+ {
+#if 1
+ return true;
+#else
+ const primitive_region &this_sub
+ = (const primitive_region &)*this;
+ const primitive_region &other_sub
+ = (const primitive_region &)other;
+ return this_sub.compare_fields (other_sub);
+#endif
+ }
+ case RK_STRUCT:
+ {
+ const struct_region &this_sub
+ = (const struct_region &)*this;
+ const struct_region &other_sub
+ = (const struct_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_UNION:
+ {
+ const union_region &this_sub
+ = (const union_region &)*this;
+ const union_region &other_sub
+ = (const union_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_ARRAY:
+ {
+ const array_region &this_sub
+ = (const array_region &)*this;
+ const array_region &other_sub
+ = (const array_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_FRAME:
+ {
+ const frame_region &this_sub
+ = (const frame_region &)*this;
+ const frame_region &other_sub
+ = (const frame_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_GLOBALS:
+ {
+ const globals_region &this_sub
+ = (const globals_region &)*this;
+ const globals_region &other_sub
+ = (const globals_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_CODE:
+ {
+ const code_region &this_sub
+ = (const code_region &)*this;
+ const code_region &other_sub
+ = (const code_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_FUNCTION:
+ {
+ const function_region &this_sub
+ = (const function_region &)*this;
+ const function_region &other_sub
+ = (const function_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_STACK:
+ {
+ const stack_region &this_sub
+ = (const stack_region &)*this;
+ const stack_region &other_sub
+ = (const stack_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_ROOT:
+ {
+ const root_region &this_sub
+ = (const root_region &)*this;
+ const root_region &other_sub
+ = (const root_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_SYMBOLIC:
+ {
+ const symbolic_region &this_sub
+ = (const symbolic_region &)*this;
+ const symbolic_region &other_sub
+ = (const symbolic_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ case RK_HEAP:
+ {
+ const heap_region &this_sub
+ = (const heap_region &)*this;
+ const heap_region &other_sub
+ = (const heap_region &)other;
+ return this_sub.compare_fields (other_sub);
+ }
+ }
+}
+
+/* Get the parent region of this region. */
+
+region *
+region::get_parent_region (const region_model &model) const
+{
+ return model.get_region (m_parent_rid);
+}
+
+/* Set this region's value to RHS_SID (or potentially a variant of it,
+ for some kinds of casts). */
+
+void
+region::set_value (region_model &model, region_id this_rid, svalue_id rhs_sid,
+ region_model_context *ctxt)
+{
+ /* Handle some kinds of casting. */
+ if (m_type)
+ {
+ svalue *sval = model.get_svalue (rhs_sid);
+ if (sval->get_type ())
+ rhs_sid = model.maybe_cast (m_type, rhs_sid, ctxt);
+
+ sval = model.get_svalue (rhs_sid);
+ if (sval->get_type ())
+ gcc_assert (m_type == sval->get_type ());
+ }
+
+ m_sval_id = rhs_sid;
+
+ /* Update views.
+ If this is a view, it becomes its parent's active view.
+ If there was already an active views, invalidate its value; otherwise
+ if the parent itself had a value, invalidate it.
+ If it's not a view, then deactivate any view that is active on this
+ region. */
+ {
+ if (m_is_view)
+ become_active_view (model, this_rid);
+ else
+ {
+ deactivate_any_active_view (model);
+ gcc_assert (m_active_view_rid.null_p ());
+ }
+ }
+}
+
+/* Make this region (with id THIS_RID) the "active" view of its parent.
+ Any other active view has its value set to "unknown" and descendent values
+ cleared.
+ If there wasn't an active view, then set the parent's value to unknown, and
+ clear its descendent values (apart from this view). */
+
+void
+region::become_active_view (region_model &model, region_id this_rid)
+{
+ gcc_assert (m_is_view);
+
+ region *parent_reg = model.get_region (m_parent_rid);
+ gcc_assert (parent_reg);
+
+ region_id old_active_view_rid = parent_reg->m_active_view_rid;
+
+ if (old_active_view_rid == this_rid)
+ {
+ /* Already the active view: do nothing. */
+ return;
+ }
+
+ /* We have a change of active view. */
+ parent_reg->m_active_view_rid = this_rid;
+
+ if (old_active_view_rid.null_p ())
+ {
+ /* No previous active view, but the parent and its other children
+ might have values.
+ If so, invalidate those values - but not that of the new view. */
+ region_id_set below_region (&model);
+ model.get_descendents (m_parent_rid, &below_region, this_rid);
+ for (unsigned i = 0; i < model.get_num_regions (); i++)
+ {
+ region_id rid (region_id::from_int (i));
+ if (below_region.region_p (rid))
+ {
+ region *other_reg = model.get_region (rid);
+ other_reg->m_sval_id = svalue_id::null ();
+ }
+ }
+ region *parent = model.get_region (m_parent_rid);
+ parent->m_sval_id
+ = model.add_svalue (new unknown_svalue (parent->get_type ()));
+ }
+ else
+ {
+ /* If there was an active view, invalidate it. */
+ region *old_active_view = model.get_region (old_active_view_rid);
+ old_active_view->deactivate_view (model, old_active_view_rid);
+ }
+}
+
+/* If this region (with id THIS_RID) has an active view, deactivate it,
+ clearing m_active_view_rid. */
+
+void
+region::deactivate_any_active_view (region_model &model)
+{
+ if (m_active_view_rid.null_p ())
+ return;
+ region *view = model.get_region (m_active_view_rid);
+ view->deactivate_view (model, m_active_view_rid);
+ m_active_view_rid = region_id::null ();
+}
+
+/* Clear any values for regions below THIS_RID.
+ Set the view's value to unknown. */
+
+void
+region::deactivate_view (region_model &model, region_id this_view_rid)
+{
+ gcc_assert (is_view_p ());
+
+ /* Purge values from old_active_this_view_rid and all its
+ descendents. Potentially we could use a poison value
+ for this, but let's use unknown for now. */
+ region_id_set below_view (&model);
+ model.get_descendents (this_view_rid, &below_view, region_id::null ());
+
+ for (unsigned i = 0; i < model.get_num_regions (); i++)
+ {
+ region_id rid (region_id::from_int (i));
+ if (below_view.region_p (rid))
+ {
+ region *other_reg = model.get_region (rid);
+ other_reg->m_sval_id = svalue_id::null ();
+ }
+ }
+
+ m_sval_id = model.add_svalue (new unknown_svalue (get_type ()));
+}
+
+/* Get a value for this region, either its value if it has one,
+ or, failing that, "inherit" a value from first ancestor with a
+ non-null value.
+
+ For example, when getting the value for a local variable within
+ a stack frame that doesn't have one, the frame doesn't have a value
+ either, but the stack as a whole will have an "uninitialized" poison
+ value, so inherit that. */
+
+svalue_id
+region::get_value (region_model &model, bool non_null,
+ region_model_context *ctxt)
+{
+ /* If this region has a value, use it. */
+ if (!m_sval_id.null_p ())
+ return m_sval_id;
+
+ /* Otherwise, "inherit" value from first ancestor with a
+ non-null value. */
+
+ region *parent = model.get_region (m_parent_rid);
+ if (parent)
+ {
+ svalue_id inherited_sid
+ = parent->get_inherited_child_sid (this, model, ctxt);
+ if (!inherited_sid.null_p ())
+ return inherited_sid;
+ }
+
+ /* If a non-null value has been requested, then generate
+ a new unknown value. Store it, so that repeated reads from this
+ region will yield the same unknown value. */
+ if (non_null)
+ {
+ svalue_id unknown_sid = model.add_svalue (new unknown_svalue (m_type));
+ m_sval_id = unknown_sid;
+ return unknown_sid;
+ }
+
+ return svalue_id::null ();
+}
+
+/* Get a value for CHILD, inheriting from this region.
+
+ Recurse, so this region will inherit a value if it doesn't already
+ have one. */
+
+svalue_id
+region::get_inherited_child_sid (region *child,
+ region_model &model,
+ region_model_context *ctxt)
+{
+ if (m_sval_id.null_p ())
+ {
+ /* Recurse. */
+ if (!m_parent_rid.null_p ())
+ {
+ region *parent = model.get_region (m_parent_rid);
+ m_sval_id = parent->get_inherited_child_sid (this, model, ctxt);
+ }
+ }
+
+ if (!m_sval_id.null_p ())
+ {
+ /* Clone the parent's value, so that attempts to update it
+ (e.g giving a specific value to an inherited "uninitialized"
+ value) touch the child, and not the parent. */
+ svalue *this_value = model.get_svalue (m_sval_id);
+ svalue_id new_child_sid
+ = this_value->get_child_sid (this, child, model, ctxt);
+ if (ctxt)
+ ctxt->on_inherited_svalue (m_sval_id, new_child_sid);
+ child->m_sval_id = new_child_sid;
+ return new_child_sid;
+ }
+
+ return svalue_id::null ();
+}
+
+/* Generate a hash value for this region. The work is done by the
+ add_to_hash vfunc. */
+
+hashval_t
+region::hash () const
+{
+ inchash::hash hstate;
+ add_to_hash (hstate);
+ return hstate.end ();
+}
+
+/* Print a one-liner representation of this region to PP, assuming
+ that this region is within MODEL and its id is THIS_RID. */
+
+void
+region::print (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ this_rid.print (pp);
+ pp_string (pp, ": {");
+
+ /* vfunc. */
+ print_fields (model, this_rid, pp);
+
+ pp_string (pp, "}");
+}
+
+/* Base class implementation of region::dump_dot_to_pp vfunc. */
+
+void
+region::dump_dot_to_pp (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ this_rid.dump_node_name_to_pp (pp);
+ pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
+ "lightgrey");
+ pp_write_text_to_stream (pp);
+ print (model, this_rid, pp);
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+ pp_string (pp, "\"];");
+ pp_newline (pp);
+
+ /* Add edge to svalue. */
+ if (!m_sval_id.null_p ())
+ {
+ this_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, " -> ");
+ m_sval_id.dump_node_name_to_pp (pp);
+ pp_string (pp, ";");
+ pp_newline (pp);
+ }
+
+ /* Add edge to parent. */
+ if (!m_parent_rid.null_p ())
+ {
+ this_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, " -> ");
+ m_parent_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, ";");
+ pp_newline (pp);
+ }
+}
+
+/* Dump a tree-like ASCII-art representation of this region to PP. */
+
+void
+region::dump_to_pp (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp,
+ const char *prefix,
+ bool is_last_child) const
+{
+ print (model, this_rid, pp);
+ pp_newline (pp);
+
+ const char *new_prefix;
+ if (!m_parent_rid.null_p ())
+ new_prefix = ACONCAT ((prefix, is_last_child ? " " : "| ", NULL));
+ else
+ new_prefix = prefix;
+
+ const char *begin_color = colorize_start (pp_show_color (pp), "note");
+ const char *end_color = colorize_stop (pp_show_color (pp));
+ char *field_prefix
+ = ACONCAT ((begin_color, new_prefix, "|:", end_color, NULL));
+
+ if (!m_sval_id.null_p ())
+ {
+ pp_printf (pp, "%s sval: ", field_prefix);
+ model.get_svalue (m_sval_id)->print (model, m_sval_id, pp);
+ pp_newline (pp);
+ }
+ if (m_type)
+ {
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%s type: %qT", field_prefix, m_type);
+POP_IGNORE_WFORMAT
+ pp_newline (pp);
+ }
+
+ /* Find the children. */
+
+ auto_vec<region_id> child_rids;
+ unsigned i;
+ for (unsigned i = 0; i < model.get_num_regions (); ++i)
+ {
+ region_id rid = region_id::from_int (i);
+ region *child = model.get_region (rid);
+ if (child->m_parent_rid == this_rid)
+ child_rids.safe_push (rid);
+ }
+
+ /* Print the children, using dump_child_label to label them. */
+
+ region_id *child_rid;
+ FOR_EACH_VEC_ELT (child_rids, i, child_rid)
+ {
+ is_last_child = (i == child_rids.length () - 1);
+ if (!this_rid.null_p ())
+ {
+ const char *tail = is_last_child ? "`-" : "|-";
+ pp_printf (pp, "%r%s%s%R", "note", new_prefix, tail);
+ }
+ dump_child_label (model, this_rid, *child_rid, pp);
+ model.get_region (*child_rid)->dump_to_pp (model, *child_rid, pp,
+ new_prefix,
+ is_last_child);
+ }
+}
+
+/* Base implementation of region::dump_child_label vfunc. */
+
+void
+region::dump_child_label (const region_model &model,
+ region_id this_rid ATTRIBUTE_UNUSED,
+ region_id child_rid,
+ pretty_printer *pp) const
+{
+ region *child = model.get_region (child_rid);
+ if (child->m_is_view)
+ {
+ gcc_assert (TYPE_P (child->get_type ()));
+ if (m_active_view_rid == child_rid)
+ pp_string (pp, "active ");
+ else
+ pp_string (pp, "inactive ");
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "view as %qT: ", child->get_type ());
+POP_IGNORE_WFORMAT
+ }
+}
+
+/* Assert that this object is valid. */
+
+void
+region::validate (const region_model *model) const
+{
+ m_parent_rid.validate (*model);
+ m_sval_id.validate (*model);
+ unsigned i;
+ region_id *view_rid;
+ FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+ {
+ gcc_assert (!view_rid->null_p ());
+ view_rid->validate (*model);
+ }
+ m_active_view_rid.validate (*model);
+}
+
+/* Apply MAP to svalue_ids to this region. This updates the value
+ for the region (if any). */
+
+void
+region::remap_svalue_ids (const svalue_id_map &map)
+{
+ map.update (&m_sval_id);
+}
+
+/* Base implementation of region::remap_region_ids vfunc; subclasses should
+ chain up to this, updating any region_id data. */
+
+void
+region::remap_region_ids (const region_id_map &map)
+{
+ map.update (&m_parent_rid);
+ unsigned i;
+ region_id *view_rid;
+ FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+ map.update (view_rid);
+ map.update (&m_active_view_rid);
+}
+
+/* Add a new region with id VIEW_RID as a view of this region. */
+
+void
+region::add_view (region_id view_rid, region_model *model)
+{
+ gcc_assert (!view_rid.null_p ());
+ region *new_view = model->get_region (view_rid);
+ new_view->m_is_view = true;
+ gcc_assert (!new_view->m_parent_rid.null_p ());
+ gcc_assert (new_view->m_sval_id.null_p ());
+
+ //gcc_assert (new_view->get_type () != NULL_TREE);
+ // TODO: this can sometimes be NULL, when viewing through a (void *)
+
+ // TODO: the type ought to not be present yet
+
+ m_view_rids.safe_push (view_rid);
+}
+
+/* Look for a view of type TYPE of this region, returning its id if found,
+ or null otherwise. */
+
+region_id
+region::get_view (tree type, region_model *model) const
+{
+ unsigned i;
+ region_id *view_rid;
+ FOR_EACH_VEC_ELT (m_view_rids, i, view_rid)
+ {
+ region *view = model->get_region (*view_rid);
+ gcc_assert (view->m_is_view);
+ if (view->get_type () == type)
+ return *view_rid;
+ }
+ return region_id::null ();
+}
+
+/* region's ctor. */
+
+region::region (region_id parent_rid, svalue_id sval_id, tree type)
+: m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type),
+ m_view_rids (), m_is_view (false), m_active_view_rid (region_id::null ())
+{
+ gcc_assert (type == NULL_TREE || TYPE_P (type));
+}
+
+/* region's copy ctor. */
+
+region::region (const region &other)
+: m_parent_rid (other.m_parent_rid), m_sval_id (other.m_sval_id),
+ m_type (other.m_type), m_view_rids (other.m_view_rids.length ()),
+ m_is_view (other.m_is_view), m_active_view_rid (other.m_active_view_rid)
+{
+ int i;
+ region_id *rid;
+ FOR_EACH_VEC_ELT (other.m_view_rids, i, rid)
+ m_view_rids.quick_push (*rid);
+}
+
+/* Base implementation of region::add_to_hash vfunc; subclasses should
+ chain up to this. */
+
+void
+region::add_to_hash (inchash::hash &hstate) const
+{
+ inchash::add (m_parent_rid, hstate);
+ inchash::add (m_sval_id, hstate);
+ hstate.add_ptr (m_type);
+ // TODO: views
+}
+
+/* Base implementation of region::print_fields vfunc. */
+
+void
+region::print_fields (const region_model &model ATTRIBUTE_UNUSED,
+ region_id this_rid ATTRIBUTE_UNUSED,
+ pretty_printer *pp) const
+{
+ pp_printf (pp, "kind: %qs", region_kind_to_str (get_kind ()));
+
+ pp_string (pp, ", parent: ");
+ m_parent_rid.print (pp);
+
+ pp_printf (pp, ", sval: ");
+ m_sval_id.print (pp);
+
+PUSH_IGNORE_WFORMAT
+ if (m_type)
+ pp_printf (pp, ", type: %qT", m_type);
+POP_IGNORE_WFORMAT
+}
+
+/* Determine if a pointer to this region must be non-NULL.
+
+ Generally, pointers to regions must be non-NULL, but pointers
+ to symbolic_regions might, in fact, be NULL.
+
+ This allows us to simulate functions like malloc and calloc with:
+ - only one "outcome" from each statement,
+ - the idea that the pointer is on the heap if non-NULL
+ - the possibility that the pointer could be NULL
+ - the idea that successive values returned from malloc are non-equal
+ - to be able to zero-fill for calloc. */
+
+bool
+region::non_null_p (const region_model &model) const
+{
+ /* Look through views to get at the underlying region. */
+ if (is_view_p ())
+ return model.get_region (m_parent_rid)->non_null_p (model);
+
+ /* Are we within a symbolic_region? If so, it could be NULL. */
+ if (const symbolic_region *sym_reg = dyn_cast_symbolic_region ())
+ {
+ if (sym_reg->m_possibly_null)
+ return false;
+ }
+
+ return true;
+}
+
+/* class primitive_region : public region. */
+
+/* Implementation of region::clone vfunc for primitive_region. */
+
+region *
+primitive_region::clone () const
+{
+ return new primitive_region (*this);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+ primitive_region. */
+
+void
+primitive_region::walk_for_canonicalization (canonicalization *) const
+{
+ /* Empty. */
+}
+
+/* class map_region : public region. */
+
+/* map_region's copy ctor. */
+
+map_region::map_region (const map_region &other)
+: region (other),
+ m_map (other.m_map)
+{
+}
+
+/* Compare the fields of this map_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+map_region::compare_fields (const map_region &other) const
+{
+ if (m_map.elements () != other.m_map.elements ())
+ return false;
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ tree key = (*iter).first;
+ region_id e = (*iter).second;
+ region_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+ if (other_slot == NULL)
+ return false;
+ if (e != *other_slot)
+ return false;
+ }
+ return true;
+}
+
+/* Implementation of region::print_fields vfunc for map_region. */
+
+void
+map_region::print_fields (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::print_fields (model, this_rid, pp);
+ pp_string (pp, ", map: {");
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ if (iter != m_map.begin ())
+ pp_string (pp, ", ");
+ tree expr = (*iter).first;
+ region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qE: ", expr);
+POP_IGNORE_WFORMAT
+ child_rid.print (pp);
+ }
+ pp_string (pp, "}");
+}
+
+/* Implementation of region::dump_dot_to_pp vfunc for map_region. */
+
+void
+map_region::dump_dot_to_pp (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::dump_dot_to_pp (model, this_rid, pp);
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ // TODO: add nodes/edges to label things
+
+ tree expr = (*iter).first;
+ region_id child_rid = (*iter).second;
+
+ pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
+ pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qE", expr);
+POP_IGNORE_WFORMAT
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+ pp_string (pp, "\"];");
+ pp_newline (pp);
+
+ pp_printf (pp, "rid_label_%i", child_rid.as_int ());
+ pp_string (pp, " -> ");
+ child_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, ";");
+ pp_newline (pp);
+ }
+}
+
+/* Implementation of region::dump_child_label vfunc for map_region. */
+
+void
+map_region::dump_child_label (const region_model &model,
+ region_id this_rid,
+ region_id child_rid,
+ pretty_printer *pp) const
+{
+ region::dump_child_label (model, this_rid, child_rid, pp);
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ if (child_rid == (*iter).second)
+ {
+ tree key = (*iter).first;
+PUSH_IGNORE_WFORMAT
+ if (DECL_P (key))
+ pp_printf (pp, "%qD: ", key);
+ else
+ pp_printf (pp, "%qE: ", key);
+POP_IGNORE_WFORMAT
+ }
+ }
+}
+
+/* Look for a child region for KEY within this map_region.
+ If it doesn't already exist, create a child map_region, using TYPE for
+ its type.
+ Return the region_id of the child (whether pre-existing, or
+ newly-created). */
+
+region_id
+map_region::get_or_create (region_model *model,
+ region_id this_rid,
+ tree key,
+ tree type)
+{
+ gcc_assert (key);
+ gcc_assert (valid_key_p (key));
+ region_id *slot = m_map.get (key);
+ if (slot)
+ return *slot;
+ region_id child_rid = model->add_region_for_type (this_rid, type);
+ m_map.put (key, child_rid);
+ return child_rid;
+}
+
+/* Get the region_id for the child region for KEY within this
+ MAP_REGION, or NULL if there is no such child region. */
+
+region_id *
+map_region::get (tree key)
+{
+ gcc_assert (key);
+ gcc_assert (valid_key_p (key));
+ region_id *slot = m_map.get (key);
+ return slot;
+}
+
+/* Implementation of region::add_to_hash vfunc for map_region. */
+
+void
+map_region::add_to_hash (inchash::hash &hstate) const
+{
+ region::add_to_hash (hstate);
+ // TODO
+}
+
+/* Implementation of region::remap_region_ids vfunc for map_region. */
+
+void
+map_region::remap_region_ids (const region_id_map &map)
+{
+ region::remap_region_ids (map);
+
+ /* Remap the region ids within the map entries. */
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end (); ++iter)
+ map.update (&(*iter).second);
+}
+
+/* Remove the binding of KEY to its child region (but not the
+ child region itself).
+ For use when purging unneeded SSA names. */
+
+void
+map_region::unbind (tree key)
+{
+ gcc_assert (key);
+ gcc_assert (valid_key_p (key));
+ m_map.remove (key);
+}
+
+/* Look for a child region with id CHILD_RID within this map_region.
+ If one is found, return its tree key, otherwise return NULL_TREE. */
+
+tree
+map_region::get_tree_for_child_region (region_id child_rid) const
+{
+ // TODO: do we want to store an inverse map?
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ tree key = (*iter).first;
+ region_id r = (*iter).second;
+ if (r == child_rid)
+ return key;
+ }
+
+ return NULL_TREE;
+}
+
+/* Look for a child region CHILD within this map_region.
+ If one is found, return its tree key, otherwise return NULL_TREE. */
+
+tree
+map_region::get_tree_for_child_region (region *child,
+ const region_model &model) const
+{
+ // TODO: do we want to store an inverse map?
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ tree key = (*iter).first;
+ region_id r = (*iter).second;
+ if (model.get_region (r) == child)
+ return key;
+ }
+
+ return NULL_TREE;
+}
+
+/* Comparator for trees to impose a deterministic ordering on
+ T1 and T2. */
+
+static int
+tree_cmp (const_tree t1, const_tree t2)
+{
+ gcc_assert (t1);
+ gcc_assert (t2);
+
+ /* Test tree codes first. */
+ if (TREE_CODE (t1) != TREE_CODE (t2))
+ return TREE_CODE (t1) - TREE_CODE (t2);
+
+ /* From this point on, we know T1 and T2 have the same tree code. */
+
+ if (DECL_P (t1))
+ {
+ if (DECL_NAME (t1) && DECL_NAME (t2))
+ return strcmp (IDENTIFIER_POINTER (DECL_NAME (t1)),
+ IDENTIFIER_POINTER (DECL_NAME (t2)));
+ else
+ {
+ if (DECL_NAME (t1))
+ return -1;
+ else if (DECL_NAME (t2))
+ return 1;
+ else
+ return DECL_UID (t1) - DECL_UID (t2);
+ }
+ }
+
+ switch (TREE_CODE (t1))
+ {
+ case SSA_NAME:
+ {
+ if (SSA_NAME_VAR (t1) && SSA_NAME_VAR (t2))
+ {
+ int var_cmp = tree_cmp (SSA_NAME_VAR (t1), SSA_NAME_VAR (t2));
+ if (var_cmp)
+ return var_cmp;
+ return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+ }
+ else
+ {
+ if (SSA_NAME_VAR (t1))
+ return -1;
+ else if (SSA_NAME_VAR (t2))
+ return 1;
+ else
+ return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+ }
+ }
+ break;
+
+ case INTEGER_CST:
+ return tree_int_cst_compare (t1, t2);
+
+ case REAL_CST:
+ {
+ real_value *rv1 = TREE_REAL_CST_PTR (t1);
+ real_value *rv2 = TREE_REAL_CST_PTR (t2);
+ if (real_compare (LT_EXPR, rv1, rv2))
+ return -1;
+ if (real_compare (LT_EXPR, rv2, rv1))
+ return 1;
+ return 0;
+ }
+
+ case STRING_CST:
+ return strcmp (TREE_STRING_POINTER (t1),
+ TREE_STRING_POINTER (t2));
+
+ default:
+ gcc_unreachable ();
+ break;
+ }
+
+ gcc_unreachable ();
+
+ return 0;
+}
+
+/* qsort comparator for trees to impose a deterministic ordering on
+ P1 and P2. */
+
+static int
+tree_cmp (const void *p1, const void *p2)
+{
+ const_tree t1 = *(const_tree const *)p1;
+ const_tree t2 = *(const_tree const *)p2;
+
+ int result = tree_cmp (t1, t2);
+
+ /* Check that the ordering is symmetric */
+#if CHECKING_P
+ int reversed = tree_cmp (t2, t1);
+ gcc_assert (reversed == -result);
+#endif
+
+ /* We should only have 0 for equal pairs. */
+#if 0
+ gcc_assert (result != 0
+ || t1 == t2);
+#endif
+
+ return result;
+}
+
+/* Attempt to merge MAP_REGION_A and MAP_REGION_B into MERGED_MAP_REGION,
+ which has region_id MERGED_RID, using MERGER.
+ Return true if the merger is possible, false otherwise. */
+
+bool
+map_region::can_merge_p (const map_region *map_region_a,
+ const map_region *map_region_b,
+ map_region *merged_map_region,
+ region_id merged_rid,
+ model_merger *merger)
+{
+ for (map_t::iterator iter = map_region_a->m_map.begin ();
+ iter != map_region_a->m_map.end ();
+ ++iter)
+ {
+ tree key_a = (*iter).first;
+ region_id rid_a = (*iter).second;
+
+ if (const region_id *slot_b
+ = const_cast<map_region *>(map_region_b)->m_map.get (key_a))
+ {
+ region_id rid_b = *slot_b;
+
+ region *child_region_a = merger->get_region_a <region> (rid_a);
+ region *child_region_b = merger->get_region_b <region> (rid_b);
+
+ gcc_assert (child_region_a->get_type ()
+ == child_region_b->get_type ());
+
+ gcc_assert (child_region_a->get_kind ()
+ == child_region_b->get_kind ());
+
+ region_id child_merged_rid
+ = merged_map_region->get_or_create (merger->m_merged_model,
+ merged_rid,
+ key_a,
+ child_region_a->get_type ());
+
+ region *child_merged_region
+ = merger->m_merged_model->get_region (child_merged_rid);
+
+ /* Consider values. */
+ svalue_id child_a_sid = child_region_a->get_value_direct ();
+ svalue_id child_b_sid = child_region_b->get_value_direct ();
+ svalue_id child_merged_sid;
+ if (!merger->can_merge_values_p (child_a_sid, child_b_sid,
+ &child_merged_sid))
+ return false;
+ if (!child_merged_sid.null_p ())
+ child_merged_region->set_value (*merger->m_merged_model,
+ child_merged_rid,
+ child_merged_sid,
+ NULL);
+
+ if (map_region *map_region_a = child_region_a->dyn_cast_map_region ())
+ {
+ /* Recurse. */
+ if (!can_merge_p (map_region_a,
+ as_a <map_region *> (child_region_b),
+ as_a <map_region *> (child_merged_region),
+ child_merged_rid,
+ merger))
+ return false;
+ }
+
+ }
+ else
+ {
+ /* TODO: region is present in A, but absent in B. */
+ }
+ }
+
+ /* TODO: check for keys in B that aren't in A. */
+
+ return true;
+}
+
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+ map_region. */
+
+void
+map_region::walk_for_canonicalization (canonicalization *c) const
+{
+ auto_vec<tree> keys (m_map.elements ());
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ tree key_a = (*iter).first;
+ keys.quick_push (key_a);
+ }
+ keys.qsort (tree_cmp);
+
+ unsigned i;
+ tree key;
+ FOR_EACH_VEC_ELT (keys, i, key)
+ {
+ region_id rid = *const_cast<map_region *>(this)->m_map.get (key);
+ c->walk_rid (rid);
+ }
+}
+
+/* For debugging purposes: look for a child region for a decl named
+ IDENTIFIER (or an SSA_NAME for such a decl), returning its value,
+ or svalue_id::null if none are found. */
+
+svalue_id
+map_region::get_value_by_name (tree identifier,
+ const region_model &model) const
+{
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ tree key = (*iter).first;
+ if (TREE_CODE (key) == SSA_NAME)
+ if (SSA_NAME_VAR (key))
+ key = SSA_NAME_VAR (key);
+ if (DECL_P (key))
+ if (DECL_NAME (key) == identifier)
+ {
+ region_id rid = (*iter).second;
+ region *region = model.get_region (rid);
+ return region->get_value (const_cast<region_model &>(model),
+ false, NULL);
+ }
+ }
+ return svalue_id::null ();
+}
+
+/* class struct_or_union_region : public map_region. */
+
+/* Implementation of map_region::valid_key_p vfunc for
+ struct_or_union_region. */
+
+bool
+struct_or_union_region::valid_key_p (tree key) const
+{
+ return TREE_CODE (key) == FIELD_DECL;
+}
+
+/* Compare the fields of this struct_or_union_region with OTHER, returning
+ true if they are equal.
+ For use by region::operator==. */
+
+bool
+struct_or_union_region::compare_fields (const struct_or_union_region &other)
+ const
+{
+ return map_region::compare_fields (other);
+}
+
+/* class struct_region : public struct_or_union_region. */
+
+/* Implementation of region::clone vfunc for struct_region. */
+
+region *
+struct_region::clone () const
+{
+ return new struct_region (*this);
+}
+
+/* Compare the fields of this struct_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+struct_region::compare_fields (const struct_region &other) const
+{
+ return struct_or_union_region::compare_fields (other);
+}
+
+/* class union_region : public struct_or_union_region. */
+
+/* Implementation of region::clone vfunc for union_region. */
+
+region *
+union_region::clone () const
+{
+ return new union_region (*this);
+}
+
+/* Compare the fields of this union_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+union_region::compare_fields (const union_region &other) const
+{
+ return struct_or_union_region::compare_fields (other);
+}
+
+/* class frame_region : public map_region. */
+
+/* Compare the fields of this frame_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+frame_region::compare_fields (const frame_region &other) const
+{
+ if (!map_region::compare_fields (other))
+ return false;
+ if (m_fun != other.m_fun)
+ return false;
+ if (m_depth != other.m_depth)
+ return false;
+ return true;
+}
+
+/* Implementation of region::clone vfunc for frame_region. */
+
+region *
+frame_region::clone () const
+{
+ return new frame_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for frame_region. */
+
+bool
+frame_region::valid_key_p (tree key) const
+{
+ // TODO: could also check that VAR_DECLs are locals
+ return (TREE_CODE (key) == PARM_DECL
+ || TREE_CODE (key) == VAR_DECL
+ || TREE_CODE (key) == SSA_NAME
+ || TREE_CODE (key) == RESULT_DECL);
+}
+
+/* Implementation of region::print_fields vfunc for frame_region. */
+
+void
+frame_region::print_fields (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ map_region::print_fields (model, this_rid, pp);
+ pp_printf (pp, ", function: %qs, depth: %i", function_name (m_fun), m_depth);
+}
+
+/* Implementation of region::add_to_hash vfunc for frame_region. */
+
+void
+frame_region::add_to_hash (inchash::hash &hstate) const
+{
+ map_region::add_to_hash (hstate);
+ hstate.add_ptr (m_fun);
+ hstate.add_int (m_depth);
+}
+
+/* class globals_region : public scope_region. */
+
+/* Compare the fields of this globals_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+globals_region::compare_fields (const globals_region &other) const
+{
+ return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for globals_region. */
+
+region *
+globals_region::clone () const
+{
+ return new globals_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for globals_region. */
+
+bool
+globals_region::valid_key_p (tree key) const
+{
+ return TREE_CODE (key) == VAR_DECL;
+}
+
+/* class code_region : public map_region. */
+
+/* Compare the fields of this code_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+code_region::compare_fields (const code_region &other) const
+{
+ return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for code_region. */
+
+region *
+code_region::clone () const
+{
+ return new code_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for code_region. */
+
+bool
+code_region::valid_key_p (tree key) const
+{
+ return TREE_CODE (key) == FUNCTION_DECL;
+}
+
+/* class array_region : public region. */
+
+/* array_region's copy ctor. */
+
+array_region::array_region (const array_region &other)
+: region (other),
+ m_map (other.m_map)
+{
+}
+
+/* Get a child region for the element with index INDEX_SID. */
+
+region_id
+array_region::get_element (region_model *model,
+ region_id this_rid,
+ svalue_id index_sid,
+ region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+ tree element_type = TREE_TYPE (get_type ());
+ svalue *index_sval = model->get_svalue (index_sid);
+ if (tree cst_index = index_sval->maybe_get_constant ())
+ {
+ key_t key = key_from_constant (cst_index);
+ region_id element_rid
+ = get_or_create (model, this_rid, key, element_type);
+ return element_rid;
+ }
+
+ return model->get_or_create_view (this_rid, element_type);
+}
+
+/* Implementation of region::clone vfunc for array_region. */
+
+region *
+array_region::clone () const
+{
+ return new array_region (*this);
+}
+
+/* Compare the fields of this array_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+array_region::compare_fields (const array_region &other) const
+{
+ if (m_map.elements () != other.m_map.elements ())
+ return false;
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ int key = (*iter).first;
+ region_id e = (*iter).second;
+ region_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+ if (other_slot == NULL)
+ return false;
+ if (e != *other_slot)
+ return false;
+ }
+ return true;
+}
+
+/* Implementation of region::print_fields vfunc for array_region. */
+
+void
+array_region::print_fields (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::print_fields (model, this_rid, pp);
+ pp_string (pp, ", array: {");
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ if (iter != m_map.begin ())
+ pp_string (pp, ", ");
+ int key = (*iter).first;
+ region_id child_rid = (*iter).second;
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "[%i]: ", key);
+POP_IGNORE_WFORMAT
+ child_rid.print (pp);
+ }
+ pp_string (pp, "}");
+}
+
+/* Implementation of region::dump_dot_to_pp vfunc for array_region. */
+
+void
+array_region::dump_dot_to_pp (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::dump_dot_to_pp (model, this_rid, pp);
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ // TODO: add nodes/edges to label things
+
+ int key = (*iter).first;
+ region_id child_rid = (*iter).second;
+
+ pp_printf (pp, "rid_label_%i [label=\"", child_rid.as_int ());
+ pp_write_text_to_stream (pp);
+PUSH_IGNORE_WFORMAT
+ pp_printf (pp, "%qi", key);
+POP_IGNORE_WFORMAT
+ pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+ pp_string (pp, "\"];");
+ pp_newline (pp);
+
+ pp_printf (pp, "rid_label_%i", child_rid.as_int ());
+ pp_string (pp, " -> ");
+ child_rid.dump_node_name_to_pp (pp);
+ pp_string (pp, ";");
+ pp_newline (pp);
+ }
+}
+
+/* Implementation of region::dump_child_label vfunc for array_region. */
+
+void
+array_region::dump_child_label (const region_model &model,
+ region_id this_rid,
+ region_id child_rid,
+ pretty_printer *pp) const
+{
+ region::dump_child_label (model, this_rid, child_rid, pp);
+
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ if (child_rid == (*iter).second)
+ {
+ int key = (*iter).first;
+ pp_printf (pp, "[%i]: ", key);
+ }
+ }
+}
+
+/* Look for a child region for KEY within this array_region.
+ If it doesn't already exist, create a child array_region, using TYPE for
+ its type.
+ Return the region_id of the child (whether pre-existing, or
+ newly-created). */
+
+region_id
+array_region::get_or_create (region_model *model,
+ region_id this_rid,
+ key_t key,
+ tree type)
+{
+ region_id *slot = m_map.get (key);
+ if (slot)
+ return *slot;
+ region_id child_rid = model->add_region_for_type (this_rid, type);
+ m_map.put (key, child_rid);
+ return child_rid;
+}
+
+/* Get the region_id for the child region for KEY within this
+ ARRAY_REGION, or NULL if there is no such child region. */
+
+region_id *
+array_region::get (key_t key)
+{
+ region_id *slot = m_map.get (key);
+ return slot;
+}
+
+/* Implementation of region::add_to_hash vfunc for array_region. */
+
+void
+array_region::add_to_hash (inchash::hash &hstate) const
+{
+ region::add_to_hash (hstate);
+ // TODO
+}
+
+/* Implementation of region::remap_region_ids vfunc for array_region. */
+
+void
+array_region::remap_region_ids (const region_id_map &map)
+{
+ region::remap_region_ids (map);
+
+ /* Remap the region ids within the map entries. */
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end (); ++iter)
+ map.update (&(*iter).second);
+}
+
+/* Look for a child region with id CHILD_RID within this array_region.
+ If one is found, write its key to *OUT and return true,
+ otherwise return false. */
+
+bool
+array_region::get_key_for_child_region (region_id child_rid, key_t *out) const
+{
+ // TODO: do we want to store an inverse map?
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ key_t key = (*iter).first;
+ region_id r = (*iter).second;
+ if (r == child_rid)
+ {
+ *out = key;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* qsort comparator for int. */
+
+static int
+int_cmp (const void *p1, const void *p2)
+{
+ int i1 = *(const int *)p1;
+ int i2 = *(const int *)p2;
+
+ return i1 - i2;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+ array_region. */
+
+void
+array_region::walk_for_canonicalization (canonicalization *c) const
+{
+ auto_vec<int> keys (m_map.elements ());
+ for (map_t::iterator iter = m_map.begin ();
+ iter != m_map.end ();
+ ++iter)
+ {
+ int key_a = (*iter).first;
+ keys.quick_push (key_a);
+ }
+ keys.qsort (int_cmp);
+
+ unsigned i;
+ int key;
+ FOR_EACH_VEC_ELT (keys, i, key)
+ {
+ region_id rid = *const_cast<array_region *>(this)->m_map.get (key);
+ c->walk_rid (rid);
+ }
+}
+
+/* Convert constant CST into an array_region::key_t. */
+
+array_region::key_t
+array_region::key_from_constant (tree cst)
+{
+ gcc_assert (CONSTANT_CLASS_P (cst));
+ wide_int w = wi::to_wide (cst);
+ key_t result = w.to_shwi ();
+ return result;
+}
+
+/* class function_region : public map_region. */
+
+/* Compare the fields of this function_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+function_region::compare_fields (const function_region &other) const
+{
+ return map_region::compare_fields (other);
+}
+
+/* Implementation of region::clone vfunc for function_region. */
+
+region *
+function_region::clone () const
+{
+ return new function_region (*this);
+}
+
+/* Implementation of map_region::valid_key_p vfunc for function_region. */
+
+bool
+function_region::valid_key_p (tree key) const
+{
+ return TREE_CODE (key) == LABEL_DECL;
+}
+
+/* class stack_region : public region. */
+
+/* stack_region's copy ctor. */
+
+stack_region::stack_region (const stack_region &other)
+: region (other),
+ m_frame_rids (other.m_frame_rids.length ())
+{
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (other.m_frame_rids, i, frame_rid)
+ m_frame_rids.quick_push (*frame_rid);
+}
+
+/* Compare the fields of this stack_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+stack_region::compare_fields (const stack_region &other) const
+{
+ if (m_frame_rids.length () != other.m_frame_rids.length ())
+ return false;
+
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+ if (m_frame_rids[i] != other.m_frame_rids[i])
+ return false;
+
+ return true;
+}
+
+/* Implementation of region::clone vfunc for stack_region. */
+
+region *
+stack_region::clone () const
+{
+ return new stack_region (*this);
+}
+
+/* Implementation of region::print_fields vfunc for stack_region. */
+
+void
+stack_region::print_fields (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::print_fields (model, this_rid, pp);
+ // TODO
+}
+
+/* Implementation of region::dump_child_label vfunc for stack_region. */
+
+void
+stack_region::dump_child_label (const region_model &model,
+ region_id this_rid ATTRIBUTE_UNUSED,
+ region_id child_rid,
+ pretty_printer *pp) const
+{
+ function *fun = model.get_region<frame_region> (child_rid)->get_function ();
+ pp_printf (pp, "frame for %qs: ", function_name (fun));
+}
+
+/* Push FRAME_RID (for a frame_region) onto this stack. */
+
+void
+stack_region::push_frame (region_id frame_rid)
+{
+ m_frame_rids.safe_push (frame_rid);
+}
+
+/* Get the region_id of the top-most frame in this stack, if any. */
+
+region_id
+stack_region::get_current_frame_id () const
+{
+ if (m_frame_rids.length () > 0)
+ return m_frame_rids[m_frame_rids.length () - 1];
+ else
+ return region_id::null ();
+}
+
+/* Pop the topmost frame_region from this stack.
+
+ Purge the frame region and all its descendent regions.
+ Convert any pointers that point into such regions into
+ POISON_KIND_POPPED_STACK svalues.
+
+ Return the ID of any return value from the frame.
+
+ If PURGE, then purge all unused svalues, with the exception of any
+ return value for the frame, which is temporarily
+ preserved in case no regions reference it, so it can
+ be written into a region in the caller.
+
+ Accumulate stats on purged entities into STATS. */
+
+svalue_id
+stack_region::pop_frame (region_model *model, bool purge, purge_stats *stats,
+ region_model_context *ctxt)
+{
+ gcc_assert (m_frame_rids.length () > 0);
+
+ region_id frame_rid = get_current_frame_id ();
+ frame_region *frame = model->get_region<frame_region> (frame_rid);
+
+ /* Evaluate the result, within the callee frame. */
+ svalue_id result_sid;
+ tree fndecl = frame->get_function ()->decl;
+ tree result = DECL_RESULT (fndecl);
+ if (result && TREE_TYPE (result) != void_type_node)
+ result_sid = model->get_rvalue (result, ctxt);
+
+ /* Pop the frame RID. */
+ m_frame_rids.pop ();
+
+ model->delete_region_and_descendents (frame_rid,
+ POISON_KIND_POPPED_STACK,
+ stats,
+ ctxt ? ctxt->get_logger () : NULL);
+
+ /* Delete unused svalues, but don't delete the return value. */
+ if (purge)
+ model->purge_unused_svalues (stats, ctxt, &result_sid);
+
+ model->validate ();
+
+ return result_sid;
+}
+
+/* Implementation of region::add_to_hash vfunc for stack_region. */
+
+void
+stack_region::add_to_hash (inchash::hash &hstate) const
+{
+ region::add_to_hash (hstate);
+
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+ inchash::add (*frame_rid, hstate);
+}
+
+/* Implementation of region::remap_region_ids vfunc for stack_region. */
+
+void
+stack_region::remap_region_ids (const region_id_map &map)
+{
+ region::remap_region_ids (map);
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+ map.update (&m_frame_rids[i]);
+}
+
+/* Attempt to merge STACK_REGION_A and STACK_REGION_B using MERGER.
+ Return true if the merger is possible, false otherwise. */
+
+bool
+stack_region::can_merge_p (const stack_region *stack_region_a,
+ const stack_region *stack_region_b,
+ model_merger *merger)
+{
+ if (stack_region_a->get_num_frames ()
+ != stack_region_b->get_num_frames ())
+ return false;
+
+ region_model *merged_model = merger->m_merged_model;
+
+ region_id rid_merged_stack
+ = merged_model->get_root_region ()->ensure_stack_region (merged_model);
+
+ stack_region *merged_stack
+ = merged_model->get_region <stack_region> (rid_merged_stack);
+
+ for (unsigned i = 0; i < stack_region_a->get_num_frames (); i++)
+ {
+ region_id rid_a = stack_region_a->get_frame_rid (i);
+ frame_region *frame_a = merger->get_region_a <frame_region> (rid_a);
+
+ region_id rid_b = stack_region_b->get_frame_rid (i);
+ frame_region *frame_b = merger->get_region_b <frame_region> (rid_b);
+
+ if (frame_a->get_function () != frame_b->get_function ())
+ return false;
+ frame_region *merged_frame = new frame_region (rid_merged_stack,
+ frame_a->get_function (),
+ frame_a->get_depth ());
+ region_id rid_merged_frame = merged_model->add_region (merged_frame);
+ merged_stack->push_frame (rid_merged_frame);
+
+ if (!map_region::can_merge_p (frame_a, frame_b,
+ merged_frame, rid_merged_frame,
+ merger))
+ return false;
+ }
+
+ return true;
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+ stack_region. */
+
+void
+stack_region::walk_for_canonicalization (canonicalization *c) const
+{
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+ c->walk_rid (*frame_rid);
+}
+
+/* For debugging purposes: look for a grandchild region within one of
+ the child frame regions, where the grandchild is for a decl named
+ IDENTIFIER (or an SSA_NAME for such a decl):
+
+ stack_region
+ `-frame_region
+ `-region for decl named IDENTIFIER
+
+ returning its value, or svalue_id::null if none are found. */
+
+svalue_id
+stack_region::get_value_by_name (tree identifier,
+ const region_model &model) const
+{
+ int i;
+ region_id *frame_rid;
+ FOR_EACH_VEC_ELT (m_frame_rids, i, frame_rid)
+ {
+ frame_region *frame = model.get_region<frame_region> (*frame_rid);
+ svalue_id sid = frame->get_value_by_name (identifier, model);
+ if (!sid.null_p ())
+ return sid;
+ }
+
+ return svalue_id::null ();
+}
+
+/* class heap_region : public region. */
+
+/* heap_region's copy ctor. */
+
+heap_region::heap_region (const heap_region &other)
+: region (other)
+{
+}
+
+/* Compare the fields of this heap_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+heap_region::compare_fields (const heap_region &) const
+{
+ /* Empty. */
+ return true;
+}
+
+/* Implementation of region::clone vfunc for heap_region. */
+
+region *
+heap_region::clone () const
+{
+ return new heap_region (*this);
+}
+
+/* Implementation of region::walk_for_canonicalization vfunc for
+ heap_region. */
+
+void
+heap_region::walk_for_canonicalization (canonicalization *) const
+{
+ /* Empty. */
+}
+
+/* class root_region : public region. */
+
+/* root_region's default ctor. */
+
+root_region::root_region ()
+: region (region_id::null (),
+ svalue_id::null (),
+ NULL_TREE)
+{
+}
+
+/* root_region's copy ctor. */
+
+root_region::root_region (const root_region &other)
+: region (other),
+ m_stack_rid (other.m_stack_rid),
+ m_globals_rid (other.m_globals_rid),
+ m_code_rid (other.m_code_rid),
+ m_heap_rid (other.m_heap_rid)
+{
+}
+
+/* Compare the fields of this root_region with OTHER, returning true
+ if they are equal.
+ For use by region::operator==. */
+
+bool
+root_region::compare_fields (const root_region &other) const
+{
+ if (m_stack_rid != other.m_stack_rid)
+ return false;
+ if (m_globals_rid != other.m_globals_rid)
+ return false;
+ if (m_code_rid != other.m_code_rid)
+ return false;
+ if (m_heap_rid != other.m_heap_rid)
+ return false;
+ return true;
+}
+
+/* Implementation of region::clone vfunc for root_region. */
+
+region *
+root_region::clone () const
+{
+ return new root_region (*this);
+}
+
+/* Implementation of region::print_fields vfunc for root_region. */
+
+void
+root_region::print_fields (const region_model &model,
+ region_id this_rid,
+ pretty_printer *pp) const
+{
+ region::print_fields (model, this_rid, pp);
+ // TODO
+}
+
+/* Implementation of region::dump_child_label vfunc for root_region. */
+
+void
+root_region::dump_child_label (const region_model &model ATTRIBUTE_UNUSED,
+ region_id this_rid ATTRIBUTE_UNUSED,
+ region_id child_rid,
+ pretty_printer *pp) const
+{
+ if (child_rid == m_stack_rid)
+ pp_printf (pp, "stack: ");
+ else if (child_rid == m_globals_rid)
+ pp_printf (pp, "globals: ");
+ else if (child_rid == m_code_rid)
+ pp_printf (pp, "code: ");
+ else if (child_rid == m_heap_rid)
+ pp_printf (pp, "heap: ");
+}
+
+/* Create a new frame_region for a call to FUN and push it onto
+ the stack.
+
+ If ARG_SIDS is non-NULL, use it to populate the parameters
+ in the new frame.
+ Otherwise, populate them with unknown values.
+
+ Return the region_id of the new frame. */
+
+region_id
+root_region::push_frame (region_model *model, function *fun,
+ vec<svalue_id> *arg_sids,
+ region_model_context *ctxt)
+{
+ gcc_assert (fun);
+ /* arg_sids can be NULL. */
+
+ ensure_stack_region (model);
+ stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+
+ frame_region *region = new frame_region (m_stack_rid, fun,
+ stack->get_num_frames ());
+ region_id frame_rid = model->add_region (region);
+
+ // TODO: unify these cases by building a vec of unknown?
+
+ if (arg_sids)
+ {
+ /* Arguments supplied from a caller frame. */
+
+ tree fndecl = fun->decl;
+ unsigned idx = 0;
+ for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+ iter_parm = DECL_CHAIN (iter_parm), ++idx)
+ {
+ /* If there's a mismatching declaration, the call stmt might
+ not have enough args. Handle this case by leaving the
+ rest of the params as uninitialized. */
+ if (idx >= arg_sids->length ())
+ break;
+ svalue_id arg_sid = (*arg_sids)[idx];
+ region_id parm_rid
+ = region->get_or_create (model, frame_rid, iter_parm,
+ TREE_TYPE (iter_parm));
+ model->set_value (parm_rid, arg_sid, ctxt);
+
+ /* Also do it for default SSA name (sharing the same unknown
+ value). */
+ tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+ if (parm_default_ssa)
+ {
+ region_id defssa_rid
+ = region->get_or_create (model, frame_rid, parm_default_ssa,
+ TREE_TYPE (iter_parm));
+ model->set_value (defssa_rid, arg_sid, ctxt);
+ }
+ }
+ }
+ else
+ {
+ /* No known arguments (a top-level call within the analysis). */
+
+ /* Params have a defined, unknown value; they should not inherit
+ from the poisoned uninit value. */
+ tree fndecl = fun->decl;
+ for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+ iter_parm = DECL_CHAIN (iter_parm))
+ {
+ region_id parm_rid
+ = region->get_or_create (model, frame_rid, iter_parm,
+ TREE_TYPE (iter_parm));
+ svalue_id parm_sid
+ = model->set_to_new_unknown_value (parm_rid, TREE_TYPE (iter_parm),
+ ctxt);
+
+ /* Also do it for default SSA name (sharing the same unknown
+ value). */
+ tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+ if (parm_default_ssa)
+ {
+ region_id defssa_rid
+ = region->get_or_create (model, frame_rid, parm_default_ssa,
+ TREE_TYPE (iter_parm));
+ model->get_region (defssa_rid)->set_value (*model, defssa_rid,
+ parm_sid, ctxt);
+ }
+ }
+ }
+
+ stack->push_frame (frame_rid);
+
+ return frame_rid;
+}
+
+/* Get the region_id of the top-most frame in this root_region's stack,
+ if any. */
+
+region_id
+root_region::get_current_frame_id (const region_model &model) const
+{
+ stack_region *stack = model.get_region <stack_region> (m_stack_rid);
+ if (stack)
+ return stack->get_current_frame_id ();
+ else
+ return region_id::null ();
+}
+
+/* Pop the topmost frame_region from this root_region's stack;
+ see the comment for stack_region::pop_frame. */
+
+svalue_id
+root_region::pop_frame (region_model *model, bool purge, purge_stats *out,
+ region_model_context *ctxt)
+{
+ stack_region *stack = model->get_region <stack_region> (m_stack_rid);
+ return stack->pop_frame (model, purge, out, ctxt);
+}
+
+/* Return the region_id of the stack region, creating it if doesn't
+ already exist. */
+
+region_id
+root_region::ensure_stack_region (region_model *model)
+{
+ if (m_stack_rid.null_p ())
+ {
+ svalue_id uninit_sid
+ = model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT,
+ NULL_TREE));
+ m_stack_rid
+ = model->add_region (new stack_region (model->get_root_rid (),
+ uninit_sid));
+ }
+ return m_stack_rid;
+}
+
+/* Return the stack region (which could be NULL). */
+
+stack_region *
+root_region::get_stack_region (const region_model *model) const
+{
+ return model->get_region <stack_region> (m_stack_rid);
+}
+
+/* Return the region_id of the globals region, creating it if doesn't
+ already exist. */
+
+region_id
+root_region::ensure_globals_region (region_model *model)
+{
+ if (m_globals_rid.null_p ())
+ m_globals_rid
+ = model->add_region (new globals_region (model->get_root_rid ()));
+ return m_globals_rid;
+}
+
+/* Return the code region (which could be NULL). */
+
+code_region *
+root_region::get_code_region (const region_model *model) const
+{
+ return model->get_region <code_region> (m_code_rid);
+}
+
+/* Return the region_id of the code region, creating it if doesn't
+ already exist. */
+
+region_id
+root_region::ensure_code_region (region_model *model)
+{
+ if (m_code_rid.null_p ())
+ m_code_rid
+ = model->add_region (new code_region (model->get_root_rid ()));
+ return m_code_rid;
+}
+
+/* Return the globals region (which could be NULL). */
+
+globals_region *
+root_region::get_globals_region (const region_model *model) const
+{
+ return model->get_region <globals_region> (m_globals_rid);
+}
+
+/* Return the region_id of the heap region, creating it if doesn't
+ already exist. */
+
+region_id
+root_region::ensure_heap_region (region_model *model)
+{
+ if (m_heap_rid.null_p ())
+ {
+ svalue_id uninit_sid
+ = model->add_svalue (new poisoned_svalue (POISON_KIND_UNINIT,
+ NULL_TREE));
+ m_heap_rid
+ = model->add_region (new heap_region (model->get_root_rid (),
+ uninit_sid));
+ }
+ return m_heap_rid;
+}
+
+/* Return the heap region (which could be NULL). */
+
+heap_region *
+root_region::get_heap_region (const region_model *model) const
+{
+ return model->get_region <heap_region> (m_heap_rid);
+}
+
+/* Implementati[...]
[diff truncated at 524288 bytes]
More information about the Gcc-cvs
mailing list