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.
This commit is contained in:
parent
08c8c973c0
commit
757bf1dff5
222 changed files with 40744 additions and 8 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
56
gcc/analyzer/ChangeLog
Normal file
56
gcc/analyzer/ChangeLog
Normal file
|
@ -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.
|
130
gcc/analyzer/analysis-plan.cc
Normal file
130
gcc/analyzer/analysis-plan.cc
Normal file
|
@ -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 */
|
56
gcc/analyzer/analysis-plan.h
Normal file
56
gcc/analyzer/analysis-plan.h
Normal file
|
@ -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 */
|
224
gcc/analyzer/analyzer-logging.cc
Normal file
224
gcc/analyzer/analyzer-logging.cc
Normal file
|
@ -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 */
|
260
gcc/analyzer/analyzer-logging.h
Normal file
260
gcc/analyzer/analyzer-logging.h
Normal file
|
@ -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 */
|
102
gcc/analyzer/analyzer-pass.cc
Normal file
102
gcc/analyzer/analyzer-pass.cc
Normal file
|
@ -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);
|
||||
}
|
61
gcc/analyzer/analyzer-selftests.cc
Normal file
61
gcc/analyzer/analyzer-selftests.cc
Normal file
|
@ -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 */
|
44
gcc/analyzer/analyzer-selftests.h
Normal file
44
gcc/analyzer/analyzer-selftests.h
Normal file
|
@ -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 */
|
151
gcc/analyzer/analyzer.cc
Normal file
151
gcc/analyzer/analyzer.cc
Normal file
|
@ -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 */
|
122
gcc/analyzer/analyzer.h
Normal file
122
gcc/analyzer/analyzer.h
Normal file
|
@ -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 */
|
181
gcc/analyzer/analyzer.opt
Normal file
181
gcc/analyzer/analyzer.opt
Normal file
|
@ -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.
|
233
gcc/analyzer/call-string.cc
Normal file
233
gcc/analyzer/call-string.cc
Normal file
|
@ -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 */
|
76
gcc/analyzer/call-string.h
Normal file
76
gcc/analyzer/call-string.h
Normal file
|
@ -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 */
|
957
gcc/analyzer/checker-path.cc
Normal file
957
gcc/analyzer/checker-path.cc
Normal file
|
@ -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 */
|
586
gcc/analyzer/checker-path.h
Normal file
586
gcc/analyzer/checker-path.h
Normal file
|
@ -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 */
|
2260
gcc/analyzer/constraint-manager.cc
Normal file
2260
gcc/analyzer/constraint-manager.cc
Normal file
File diff suppressed because it is too large
Load diff
246
gcc/analyzer/constraint-manager.h
Normal file
246
gcc/analyzer/constraint-manager.h
Normal file
|
@ -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 */
|
1243
gcc/analyzer/diagnostic-manager.cc
Normal file
1243
gcc/analyzer/diagnostic-manager.cc
Normal file
File diff suppressed because it is too large
Load diff
135
gcc/analyzer/diagnostic-manager.h
Normal file
135
gcc/analyzer/diagnostic-manager.h
Normal file
|
@ -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 */
|
3614
gcc/analyzer/engine.cc
Normal file
3614
gcc/analyzer/engine.cc
Normal file
File diff suppressed because it is too large
Load diff
26
gcc/analyzer/engine.h
Normal file
26
gcc/analyzer/engine.h
Normal file
|
@ -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 */
|
829
gcc/analyzer/exploded-graph.h
Normal file
829
gcc/analyzer/exploded-graph.h
Normal file
|
@ -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 */
|
70
gcc/analyzer/pending-diagnostic.cc
Normal file
70
gcc/analyzer/pending-diagnostic.cc
Normal file
|
@ -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 */
|
266
gcc/analyzer/pending-diagnostic.h
Normal file
266
gcc/analyzer/pending-diagnostic.h
Normal file
|
@ -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 */
|
554
gcc/analyzer/program-point.cc
Normal file
554
gcc/analyzer/program-point.cc
Normal file
|
@ -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 */
|
310
gcc/analyzer/program-point.h
Normal file
310
gcc/analyzer/program-point.h
Normal file
|
@ -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 */
|
1356
gcc/analyzer/program-state.cc
Normal file
1356
gcc/analyzer/program-state.cc
Normal file
File diff suppressed because it is too large
Load diff
363
gcc/analyzer/program-state.h
Normal file
363
gcc/analyzer/program-state.h
Normal file
|
@ -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 */
|
7785
gcc/analyzer/region-model.cc
Normal file
7785
gcc/analyzer/region-model.cc
Normal file
File diff suppressed because it is too large
Load diff
2057
gcc/analyzer/region-model.h
Normal file
2057
gcc/analyzer/region-model.h
Normal file
File diff suppressed because it is too large
Load diff
339
gcc/analyzer/sm-file.cc
Normal file
339
gcc/analyzer/sm-file.cc
Normal file
|
@ -0,0 +1,339 @@
|
|||
/* A state machine for detecting misuses of <stdio.h>'s FILE * API.
|
||||
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 "options.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.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
|
||||
|
||||
namespace {
|
||||
|
||||
/* A state machine for detecting misuses of <stdio.h>'s FILE * API. */
|
||||
|
||||
class fileptr_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
fileptr_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
/* State for a FILE * returned from fopen that hasn't been checked for
|
||||
NULL.
|
||||
It could be an open stream, or could be NULL. */
|
||||
state_t m_unchecked;
|
||||
|
||||
/* State for a FILE * that's known to be NULL. */
|
||||
state_t m_null;
|
||||
|
||||
/* State for a FILE * that's known to be a non-NULL open stream. */
|
||||
state_t m_nonnull;
|
||||
|
||||
/* State for a FILE * that's had fclose called on it. */
|
||||
state_t m_closed;
|
||||
|
||||
/* Stop state, for a FILE * we don't want to track any more. */
|
||||
state_t m_stop;
|
||||
};
|
||||
|
||||
/* Base class for diagnostics relative to fileptr_state_machine. */
|
||||
|
||||
class file_diagnostic : public pending_diagnostic
|
||||
{
|
||||
public:
|
||||
file_diagnostic (const fileptr_state_machine &sm, tree arg)
|
||||
: m_sm (sm), m_arg (arg)
|
||||
{}
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
|
||||
{
|
||||
return m_arg == ((const file_diagnostic &)base_other).m_arg;
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
OVERRIDE
|
||||
{
|
||||
if (change.m_old_state == m_sm.m_start
|
||||
&& change.m_new_state == m_sm.m_unchecked)
|
||||
// TODO: verify that it's the fopen stmt, not a copy
|
||||
return label_text::borrow ("opened here");
|
||||
if (change.m_old_state == m_sm.m_unchecked
|
||||
&& change.m_new_state == m_sm.m_nonnull)
|
||||
return change.formatted_print ("assuming %qE is non-NULL",
|
||||
change.m_expr);
|
||||
if (change.m_new_state == m_sm.m_null)
|
||||
return change.formatted_print ("assuming %qE is NULL",
|
||||
change.m_expr);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
protected:
|
||||
const fileptr_state_machine &m_sm;
|
||||
tree m_arg;
|
||||
};
|
||||
|
||||
class double_fclose : public file_diagnostic
|
||||
{
|
||||
public:
|
||||
double_fclose (const fileptr_state_machine &sm, tree arg)
|
||||
: file_diagnostic (sm, arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
return warning_at (rich_loc, OPT_Wanalyzer_double_fclose,
|
||||
"double %<fclose%> of FILE %qE",
|
||||
m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_closed)
|
||||
{
|
||||
m_first_fclose_event = change.m_event_id;
|
||||
return change.formatted_print ("first %qs here", "fclose");
|
||||
}
|
||||
return file_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_first_fclose_event.known_p ())
|
||||
return ev.formatted_print ("second %qs here; first %qs was at %@",
|
||||
"fclose", "fclose",
|
||||
&m_first_fclose_event);
|
||||
return ev.formatted_print ("second %qs here", "fclose");
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_first_fclose_event;
|
||||
};
|
||||
|
||||
class file_leak : public file_diagnostic
|
||||
{
|
||||
public:
|
||||
file_leak (const fileptr_state_machine &sm, tree arg)
|
||||
: file_diagnostic (sm, arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
/* CWE-775: "Missing Release of File Descriptor or Handle after
|
||||
Effective Lifetime". */
|
||||
m.add_cwe (775);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_file_leak,
|
||||
"leak of FILE %qE",
|
||||
m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_unchecked)
|
||||
{
|
||||
m_fopen_event = change.m_event_id;
|
||||
return label_text::borrow ("opened here");
|
||||
}
|
||||
return file_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_fopen_event.known_p ())
|
||||
return ev.formatted_print ("%qE leaks here; was opened at %@",
|
||||
ev.m_expr, &m_fopen_event);
|
||||
else
|
||||
return ev.formatted_print ("%qE leaks here", ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_fopen_event;
|
||||
};
|
||||
|
||||
/* fileptr_state_machine's ctor. */
|
||||
|
||||
fileptr_state_machine::fileptr_state_machine (logger *logger)
|
||||
: state_machine ("file", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
m_unchecked = add_state ("unchecked");
|
||||
m_null = add_state ("null");
|
||||
m_nonnull = add_state ("nonnull");
|
||||
m_closed = add_state ("closed");
|
||||
m_stop = add_state ("stop");
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for fileptr_state_machine. */
|
||||
|
||||
bool
|
||||
fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
{
|
||||
if (is_named_call_p (callee_fndecl, "fopen", call, 2))
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "fclose", call, 1))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
|
||||
|
||||
// TODO: is it safe to call fclose (NULL) ?
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_closed);
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_null, m_closed);
|
||||
|
||||
sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
|
||||
new double_fclose (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: operations on closed file
|
||||
// etc
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for
|
||||
fileptr_state_machine.
|
||||
Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */
|
||||
|
||||
void
|
||||
fileptr_state_machine::on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const
|
||||
{
|
||||
if (!zerop (rhs))
|
||||
return;
|
||||
|
||||
// TODO: has to be a FILE *, specifically
|
||||
if (TREE_CODE (TREE_TYPE (lhs)) != POINTER_TYPE)
|
||||
return;
|
||||
|
||||
// TODO: has to be a FILE *, specifically
|
||||
if (TREE_CODE (TREE_TYPE (rhs)) != POINTER_TYPE)
|
||||
return;
|
||||
|
||||
if (op == NE_EXPR)
|
||||
{
|
||||
log ("got 'ARG != 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_nonnull);
|
||||
}
|
||||
else if (op == EQ_EXPR)
|
||||
{
|
||||
log ("got 'ARG == 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_null);
|
||||
}
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::can_purge_p vfunc for fileptr_state_machine.
|
||||
Don't allow purging of pointers in state 'unchecked' or 'nonnull'
|
||||
(to avoid false leak reports). */
|
||||
|
||||
bool
|
||||
fileptr_state_machine::can_purge_p (state_t s) const
|
||||
{
|
||||
return s != m_unchecked && s != m_nonnull;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_leak vfunc for
|
||||
fileptr_state_machine, for complaining about leaks of FILE * in
|
||||
state 'unchecked' and 'nonnull'. */
|
||||
|
||||
pending_diagnostic *
|
||||
fileptr_state_machine::on_leak (tree var) const
|
||||
{
|
||||
return new file_leak (*this, var);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_fileptr_state_machine (logger *logger)
|
||||
{
|
||||
return new fileptr_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
797
gcc/analyzer/sm-malloc.cc
Normal file
797
gcc/analyzer/sm-malloc.cc
Normal file
|
@ -0,0 +1,797 @@
|
|||
/* A state machine for detecting misuses of the malloc/free API.
|
||||
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 "options.h"
|
||||
#include "bitmap.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "diagnostic-event-id.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace {
|
||||
|
||||
/* A state machine for detecting misuses of the malloc/free API.
|
||||
|
||||
See sm-malloc.dot for an overview (keep this in-sync with that file). */
|
||||
|
||||
class malloc_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
malloc_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
/* State for a pointer returned from malloc that hasn't been checked for
|
||||
NULL.
|
||||
It could be a pointer to heap-allocated memory, or could be NULL. */
|
||||
state_t m_unchecked;
|
||||
|
||||
/* State for a pointer that's known to be NULL. */
|
||||
state_t m_null;
|
||||
|
||||
/* State for a pointer to heap-allocated memory, known to be non-NULL. */
|
||||
state_t m_nonnull;
|
||||
|
||||
/* State for a pointer to freed memory. */
|
||||
state_t m_freed;
|
||||
|
||||
/* State for a pointer that's known to not be on the heap (e.g. to a local
|
||||
or global). */
|
||||
state_t m_non_heap; // TODO: or should this be a different state machine?
|
||||
// or do we need child values etc?
|
||||
|
||||
/* Stop state, for pointers we don't want to track any more. */
|
||||
state_t m_stop;
|
||||
};
|
||||
|
||||
/* Class for diagnostics relating to malloc_state_machine. */
|
||||
|
||||
class malloc_diagnostic : public pending_diagnostic
|
||||
{
|
||||
public:
|
||||
malloc_diagnostic (const malloc_state_machine &sm, tree arg)
|
||||
: m_sm (sm), m_arg (arg)
|
||||
{}
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
|
||||
{
|
||||
return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
OVERRIDE
|
||||
{
|
||||
if (change.m_old_state == m_sm.m_start
|
||||
&& change.m_new_state == m_sm.m_unchecked)
|
||||
// TODO: verify that it's the allocation stmt, not a copy
|
||||
return label_text::borrow ("allocated here");
|
||||
if (change.m_old_state == m_sm.m_unchecked
|
||||
&& change.m_new_state == m_sm.m_nonnull)
|
||||
return change.formatted_print ("assuming %qE is non-NULL",
|
||||
change.m_expr);
|
||||
if (change.m_new_state == m_sm.m_null)
|
||||
return change.formatted_print ("assuming %qE is NULL",
|
||||
change.m_expr);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
protected:
|
||||
const malloc_state_machine &m_sm;
|
||||
tree m_arg;
|
||||
};
|
||||
|
||||
/* Concrete subclass for reporting double-free diagnostics. */
|
||||
|
||||
class double_free : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
double_free (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (415); /* CWE-415: Double Free. */
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_double_free,
|
||||
"double-%<free%> of %qE", m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_freed)
|
||||
{
|
||||
m_first_free_event = change.m_event_id;
|
||||
return change.formatted_print ("first %qs here", "free");
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_call_with_state (const evdesc::call_with_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_freed)
|
||||
return info.formatted_print
|
||||
("passing freed pointer %qE in call to %qE from %qE",
|
||||
info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_first_free_event.known_p ())
|
||||
return ev.formatted_print ("second %qs here; first %qs was at %@",
|
||||
"free", "free",
|
||||
&m_first_free_event);
|
||||
return ev.formatted_print ("second %qs here", "free");
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_first_free_event;
|
||||
};
|
||||
|
||||
/* Abstract subclass for describing possible bad uses of NULL.
|
||||
Responsible for describing the call that could return NULL. */
|
||||
|
||||
class possible_null : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
possible_null (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg)
|
||||
{}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_old_state == m_sm.m_start
|
||||
&& change.m_new_state == m_sm.m_unchecked)
|
||||
{
|
||||
m_origin_of_unchecked_event = change.m_event_id;
|
||||
return label_text::borrow ("this call could return NULL");
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_return_of_state (const evdesc::return_of_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_unchecked)
|
||||
return info.formatted_print ("possible return of NULL to %qE from %qE",
|
||||
info.m_caller_fndecl, info.m_callee_fndecl);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
protected:
|
||||
diagnostic_event_id_t m_origin_of_unchecked_event;
|
||||
};
|
||||
|
||||
/* Concrete subclass for describing dereference of a possible NULL
|
||||
value. */
|
||||
|
||||
class possible_null_deref : public possible_null
|
||||
{
|
||||
public:
|
||||
possible_null_deref (const malloc_state_machine &sm, tree arg)
|
||||
: possible_null (sm, arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
/* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (690);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_dereference,
|
||||
"dereference of possibly-NULL %qE", m_arg);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_origin_of_unchecked_event.known_p ())
|
||||
return ev.formatted_print ("%qE could be NULL: unchecked value from %@",
|
||||
ev.m_expr,
|
||||
&m_origin_of_unchecked_event);
|
||||
else
|
||||
return ev.formatted_print ("%qE could be NULL", ev.m_expr);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
|
||||
Issue a note informing that the pertinent argument must be non-NULL. */
|
||||
|
||||
static void
|
||||
inform_nonnull_attribute (tree fndecl, int arg_idx)
|
||||
{
|
||||
inform (DECL_SOURCE_LOCATION (fndecl),
|
||||
"argument %u of %qD must be non-null",
|
||||
arg_idx + 1, fndecl);
|
||||
/* Ideally we would use the location of the parm and underline the
|
||||
attribute also - but we don't have the location_t values at this point
|
||||
in the middle-end.
|
||||
For reference, the C and C++ FEs have get_fndecl_argument_location. */
|
||||
}
|
||||
|
||||
/* Concrete subclass for describing passing a possibly-NULL value to a
|
||||
function marked with __attribute__((nonnull)). */
|
||||
|
||||
class possible_null_arg : public possible_null
|
||||
{
|
||||
public:
|
||||
possible_null_arg (const malloc_state_machine &sm, tree arg,
|
||||
tree fndecl, int arg_idx)
|
||||
: possible_null (sm, arg),
|
||||
m_fndecl (fndecl), m_arg_idx (arg_idx)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const
|
||||
{
|
||||
const possible_null_arg &sub_other
|
||||
= (const possible_null_arg &)base_other;
|
||||
return (m_arg == sub_other.m_arg
|
||||
&& m_fndecl == sub_other.m_fndecl
|
||||
&& m_arg_idx == sub_other.m_arg_idx);
|
||||
}
|
||||
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
/* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */
|
||||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (690);
|
||||
bool warned
|
||||
= warning_at (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
|
||||
"use of possibly-NULL %qE where non-null expected",
|
||||
m_arg);
|
||||
if (warned)
|
||||
inform_nonnull_attribute (m_fndecl, m_arg_idx);
|
||||
return warned;
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_origin_of_unchecked_event.known_p ())
|
||||
return ev.formatted_print ("argument %u (%qE) from %@ could be NULL"
|
||||
" where non-null expected",
|
||||
m_arg_idx + 1, ev.m_expr,
|
||||
&m_origin_of_unchecked_event);
|
||||
else
|
||||
return ev.formatted_print ("argument %u (%qE) could be NULL"
|
||||
" where non-null expected",
|
||||
m_arg_idx + 1, ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
tree m_fndecl;
|
||||
int m_arg_idx;
|
||||
};
|
||||
|
||||
/* Concrete subclass for describing a dereference of a NULL value. */
|
||||
|
||||
class null_deref : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
null_deref (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg) {}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
/* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (690);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_null_dereference,
|
||||
"dereference of NULL %qE", m_arg);
|
||||
}
|
||||
|
||||
label_text describe_return_of_state (const evdesc::return_of_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_null)
|
||||
return info.formatted_print ("return of NULL to %qE from %qE",
|
||||
info.m_caller_fndecl, info.m_callee_fndecl);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("dereference of NULL %qE", ev.m_expr);
|
||||
}
|
||||
};
|
||||
|
||||
/* Concrete subclass for describing passing a NULL value to a
|
||||
function marked with __attribute__((nonnull)). */
|
||||
|
||||
class null_arg : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
null_arg (const malloc_state_machine &sm, tree arg,
|
||||
tree fndecl, int arg_idx)
|
||||
: malloc_diagnostic (sm, arg),
|
||||
m_fndecl (fndecl), m_arg_idx (arg_idx)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const
|
||||
{
|
||||
const null_arg &sub_other
|
||||
= (const null_arg &)base_other;
|
||||
return (m_arg == sub_other.m_arg
|
||||
&& m_fndecl == sub_other.m_fndecl
|
||||
&& m_arg_idx == sub_other.m_arg_idx);
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
/* CWE-690: Unchecked Return Value to NULL Pointer Dereference. */
|
||||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (690);
|
||||
bool warned = warning_at (rich_loc, m, OPT_Wanalyzer_null_argument,
|
||||
"use of NULL %qE where non-null expected", m_arg);
|
||||
if (warned)
|
||||
inform_nonnull_attribute (m_fndecl, m_arg_idx);
|
||||
return warned;
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("argument %u (%qE) NULL"
|
||||
" where non-null expected",
|
||||
m_arg_idx + 1, ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
tree m_fndecl;
|
||||
int m_arg_idx;
|
||||
};
|
||||
|
||||
class use_after_free : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
use_after_free (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
/* CWE-416: Use After Free. */
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (416);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_use_after_free,
|
||||
"use after %<free%> of %qE", m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_freed)
|
||||
{
|
||||
m_free_event = change.m_event_id;
|
||||
return label_text::borrow ("freed here");
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_free_event.known_p ())
|
||||
return ev.formatted_print ("use after %<free%> of %qE; freed at %@",
|
||||
ev.m_expr, &m_free_event);
|
||||
else
|
||||
return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_free_event;
|
||||
};
|
||||
|
||||
class malloc_leak : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
malloc_leak (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg) {}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (401);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_malloc_leak,
|
||||
"leak of %qE", m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_unchecked)
|
||||
{
|
||||
m_malloc_event = change.m_event_id;
|
||||
return label_text::borrow ("allocated here");
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
return ev.formatted_print ("%qE leaks here; was allocated at %@",
|
||||
ev.m_expr, &m_malloc_event);
|
||||
else
|
||||
return ev.formatted_print ("%qE leaks here", ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_malloc_event;
|
||||
};
|
||||
|
||||
class free_of_non_heap : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
free_of_non_heap (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
|
||||
{
|
||||
}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
|
||||
|
||||
bool subclass_equal_p (const pending_diagnostic &base_other) const
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
const free_of_non_heap &other = (const free_of_non_heap &)base_other;
|
||||
return (m_arg == other.m_arg && m_kind == other.m_kind);
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap. */
|
||||
switch (m_kind)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case KIND_UNKNOWN:
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
|
||||
"%<free%> of %qE which points to memory"
|
||||
" not on the heap",
|
||||
m_arg);
|
||||
break;
|
||||
case KIND_ALLOCA:
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
|
||||
"%<free%> of memory allocated on the stack by"
|
||||
" %qs (%qE) will corrupt the heap",
|
||||
"alloca", m_arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
/* Attempt to reconstruct what kind of pointer it is.
|
||||
(It seems neater for this to be a part of the state, though). */
|
||||
if (TREE_CODE (change.m_expr) == SSA_NAME)
|
||||
{
|
||||
gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
|
||||
if (gcall *call = dyn_cast <gcall *> (def_stmt))
|
||||
{
|
||||
if (is_special_named_call_p (call, "alloca", 1)
|
||||
|| is_special_named_call_p (call, "__builtin_alloca", 1))
|
||||
{
|
||||
m_kind = KIND_ALLOCA;
|
||||
return label_text::borrow
|
||||
("memory is allocated on the stack here");
|
||||
}
|
||||
}
|
||||
}
|
||||
return label_text::borrow ("pointer is from here");
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("call to %qs here", "free");
|
||||
}
|
||||
|
||||
private:
|
||||
enum kind
|
||||
{
|
||||
KIND_UNKNOWN,
|
||||
KIND_ALLOCA
|
||||
};
|
||||
enum kind m_kind;
|
||||
};
|
||||
|
||||
/* malloc_state_machine's ctor. */
|
||||
|
||||
malloc_state_machine::malloc_state_machine (logger *logger)
|
||||
: state_machine ("malloc", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
m_unchecked = add_state ("unchecked");
|
||||
m_null = add_state ("null");
|
||||
m_nonnull = add_state ("nonnull");
|
||||
m_freed = add_state ("freed");
|
||||
m_non_heap = add_state ("non-heap");
|
||||
m_stop = add_state ("stop");
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
|
||||
|
||||
bool
|
||||
malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
{
|
||||
if (is_named_call_p (callee_fndecl, "malloc", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "calloc", call, 2)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2))
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "alloca", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "free", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
/* start/unchecked/nonnull -> freed. */
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);
|
||||
|
||||
/* Keep state "null" as-is, rather than transitioning to "free";
|
||||
we don't want want to complain about double-free of NULL. */
|
||||
|
||||
/* freed -> stop, with warning. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
|
||||
new double_free (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
|
||||
|
||||
/* non-heap -> stop, with warning. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
|
||||
new free_of_non_heap (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Handle "__attribute__((nonnull))". */
|
||||
{
|
||||
tree fntype = TREE_TYPE (callee_fndecl);
|
||||
bitmap nonnull_args = get_nonnull_args (fntype);
|
||||
if (nonnull_args)
|
||||
{
|
||||
for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
|
||||
{
|
||||
tree arg = gimple_call_arg (stmt, i);
|
||||
if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
|
||||
continue;
|
||||
/* If we have a nonnull-args, and either all pointers, or just
|
||||
the specified pointers. */
|
||||
if (bitmap_empty_p (nonnull_args)
|
||||
|| bitmap_bit_p (nonnull_args, i))
|
||||
{
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_unchecked,
|
||||
new possible_null_arg (*this, arg, callee_fndecl, i));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
|
||||
m_nonnull);
|
||||
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_null,
|
||||
new null_arg (*this, arg, callee_fndecl, i));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
|
||||
}
|
||||
}
|
||||
BITMAP_FREE (nonnull_args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tree lhs = is_zero_assignment (stmt))
|
||||
{
|
||||
if (any_pointer_p (lhs))
|
||||
{
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_freed, m_null);
|
||||
}
|
||||
}
|
||||
|
||||
if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
|
||||
{
|
||||
enum tree_code op = gimple_assign_rhs_code (assign_stmt);
|
||||
if (op == ADDR_EXPR)
|
||||
{
|
||||
tree lhs = gimple_assign_lhs (assign_stmt);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle dereferences. */
|
||||
for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
|
||||
{
|
||||
tree op = gimple_op (stmt, i);
|
||||
if (!op)
|
||||
continue;
|
||||
if (TREE_CODE (op) == COMPONENT_REF)
|
||||
op = TREE_OPERAND (op, 0);
|
||||
|
||||
if (TREE_CODE (op) == MEM_REF)
|
||||
{
|
||||
tree arg = TREE_OPERAND (op, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
|
||||
new possible_null_deref (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_null,
|
||||
new null_deref (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
|
||||
new use_after_free (*this, arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for malloc_state_machine.
|
||||
Potentially transition state 'unchecked' to 'nonnull' or to 'null'. */
|
||||
|
||||
void
|
||||
malloc_state_machine::on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const
|
||||
{
|
||||
if (!zerop (rhs))
|
||||
return;
|
||||
|
||||
if (!any_pointer_p (lhs))
|
||||
return;
|
||||
if (!any_pointer_p (rhs))
|
||||
return;
|
||||
|
||||
if (op == NE_EXPR)
|
||||
{
|
||||
log ("got 'ARG != 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_nonnull);
|
||||
}
|
||||
else if (op == EQ_EXPR)
|
||||
{
|
||||
log ("got 'ARG == 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_null);
|
||||
}
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine.
|
||||
Don't allow purging of pointers in state 'unchecked' or 'nonnull'
|
||||
(to avoid false leak reports). */
|
||||
|
||||
bool
|
||||
malloc_state_machine::can_purge_p (state_t s) const
|
||||
{
|
||||
return s != m_unchecked && s != m_nonnull;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_leak vfunc for malloc_state_machine
|
||||
(for complaining about leaks of pointers in state 'unchecked' and
|
||||
'nonnull'). */
|
||||
|
||||
pending_diagnostic *
|
||||
malloc_state_machine::on_leak (tree var) const
|
||||
{
|
||||
return new malloc_leak (*this, var);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_malloc_state_machine (logger *logger)
|
||||
{
|
||||
return new malloc_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
89
gcc/analyzer/sm-malloc.dot
Normal file
89
gcc/analyzer/sm-malloc.dot
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* An overview of the state machine from sm-malloc.cc.
|
||||
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/>. */
|
||||
|
||||
/* Keep this in-sync with sm-malloc.cc */
|
||||
|
||||
digraph "malloc" {
|
||||
|
||||
/* STATES. */
|
||||
|
||||
/* Start state. */
|
||||
start;
|
||||
|
||||
/* State for a pointer returned from malloc that hasn't been checked for
|
||||
NULL.
|
||||
It could be a pointer to heap-allocated memory, or could be NULL. */
|
||||
unchecked;
|
||||
|
||||
/* State for a pointer that's known to be NULL. */
|
||||
null;
|
||||
|
||||
/* State for a pointer to heap-allocated memory, known to be non-NULL. */
|
||||
nonnull;
|
||||
|
||||
/* State for a pointer to freed memory. */
|
||||
freed;
|
||||
|
||||
/* State for a pointer that's known to not be on the heap (e.g. to a local
|
||||
or global). */
|
||||
non_heap;
|
||||
|
||||
/* Stop state, for pointers we don't want to track any more. */
|
||||
stop;
|
||||
|
||||
/* TRANSITIONS. */
|
||||
|
||||
start -> unchecked [label="on 'X=malloc(...);'"];
|
||||
start -> unchecked [label="on 'X=calloc(...);'"];
|
||||
|
||||
start -> non_heap [label="on 'X=alloca(...);'"];
|
||||
start -> non_heap [label="on 'X=__builtin_alloca(...);'"];
|
||||
|
||||
/* On "free". */
|
||||
start -> freed [label="on 'free(X);'"];
|
||||
unchecked -> freed [label="on 'free(X);'"];
|
||||
nonnull -> freed [label="on 'free(X);'"];
|
||||
freed -> stop [label="on 'free(X);':\n Warn('double-free')"];
|
||||
non_heap -> stop [label="on 'free(X);':\n Warn('free of non-heap')"];
|
||||
|
||||
/* Handle "__attribute__((nonnull))". */
|
||||
unchecked -> nonnull [label="on 'FN(X)' with __attribute__((nonnull)):\nWarn('possible NULL arg')"];
|
||||
null -> stop [label="on 'FN(X)' with __attribute__((nonnull)):\nWarn('NULL arg')"];
|
||||
|
||||
/* is_zero_assignment. */
|
||||
start -> null [label="on 'X = 0;'"];
|
||||
unchecked -> null [label="on 'X = 0;'"];
|
||||
nonnull -> null [label="on 'X = 0;'"];
|
||||
freed -> null [label="on 'X = 0;'"];
|
||||
|
||||
start -> non_heap [label="on 'X = &EXPR;'"];
|
||||
|
||||
/* Handle dereferences. */
|
||||
unchecked -> nonnull [label="on '*X':\nWarn('possible NULL deref')"];
|
||||
null -> stop [label="on '*X':\nWarn('NULL deref')"];
|
||||
freed -> stop [label="on '*X':\nWarn('use after free')"];
|
||||
|
||||
/* on_condition. */
|
||||
unchecked -> nonnull [label="on 'X != 0'"];
|
||||
unchecked -> null [label="on 'X == 0'"];
|
||||
|
||||
unchecked -> stop [label="on leak:\nWarn('leak')"];
|
||||
nonnull -> stop [label="on leak:\nWarn('leak')"];
|
||||
}
|
152
gcc/analyzer/sm-pattern-test.cc
Normal file
152
gcc/analyzer/sm-pattern-test.cc
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* A state machine for use in DejaGnu tests, to check that
|
||||
pattern-matching works as expected.
|
||||
|
||||
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 "tree-pretty-print.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "diagnostic-event-id.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace {
|
||||
|
||||
/* A state machine for use in DejaGnu tests, to check that
|
||||
pattern-matching works as expected. */
|
||||
|
||||
class pattern_test_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
pattern_test_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
|
||||
private:
|
||||
state_t m_start;
|
||||
};
|
||||
|
||||
class pattern_match : public pending_diagnostic_subclass<pattern_match>
|
||||
{
|
||||
public:
|
||||
pattern_match (tree lhs, enum tree_code op, tree rhs)
|
||||
: m_lhs (lhs), m_op (op), m_rhs (rhs) {}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
|
||||
|
||||
bool operator== (const pattern_match &other) const
|
||||
{
|
||||
return (m_lhs == other.m_lhs
|
||||
&& m_op == other.m_op
|
||||
&& m_rhs == other.m_rhs);
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
return warning_at (rich_loc, 0, "pattern match on %<%E %s %E%>",
|
||||
m_lhs, op_symbol_code (m_op), m_rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
tree m_lhs;
|
||||
enum tree_code m_op;
|
||||
tree m_rhs;
|
||||
};
|
||||
|
||||
pattern_test_state_machine::pattern_test_state_machine (logger *logger)
|
||||
: state_machine ("pattern-test", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
}
|
||||
|
||||
bool
|
||||
pattern_test_state_machine::on_stmt (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
|
||||
const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for
|
||||
pattern_test_state_machine.
|
||||
|
||||
Queue a pattern_match diagnostic for any comparison against a
|
||||
constant. */
|
||||
|
||||
void
|
||||
pattern_test_state_machine::on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const
|
||||
{
|
||||
if (stmt == NULL)
|
||||
return;
|
||||
|
||||
if (!CONSTANT_CLASS_P (rhs))
|
||||
return;
|
||||
|
||||
pending_diagnostic *diag = new pattern_match (lhs, op, rhs);
|
||||
sm_ctxt->warn_for_state (node, stmt, lhs, m_start, diag);
|
||||
}
|
||||
|
||||
bool
|
||||
pattern_test_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_pattern_test_state_machine (logger *logger)
|
||||
{
|
||||
return new pattern_test_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
248
gcc/analyzer/sm-sensitive.cc
Normal file
248
gcc/analyzer/sm-sensitive.cc
Normal file
|
@ -0,0 +1,248 @@
|
|||
/* An experimental state machine, for tracking exposure of sensitive
|
||||
data (e.g. through logging).
|
||||
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 "function.h"
|
||||
#include "basic-block.h"
|
||||
#include "gimple.h"
|
||||
#include "options.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "diagnostic-event-id.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace {
|
||||
|
||||
/* An experimental state machine, for tracking exposure of sensitive
|
||||
data (e.g. through logging). */
|
||||
|
||||
class sensitive_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
sensitive_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return true; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
/* State for "sensitive" data, such as a password. */
|
||||
state_t m_sensitive;
|
||||
|
||||
/* Stop state, for a value we don't want to track any more. */
|
||||
state_t m_stop;
|
||||
|
||||
private:
|
||||
void warn_for_any_exposure (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree arg) const;
|
||||
};
|
||||
|
||||
class exposure_through_output_file
|
||||
: public pending_diagnostic_subclass<exposure_through_output_file>
|
||||
{
|
||||
public:
|
||||
exposure_through_output_file (const sensitive_state_machine &sm, tree arg)
|
||||
: m_sm (sm), m_arg (arg)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE
|
||||
{
|
||||
return "exposure_through_output_file";
|
||||
}
|
||||
|
||||
bool operator== (const exposure_through_output_file &other) const
|
||||
{
|
||||
return m_arg == other.m_arg;
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
/* CWE-532: Information Exposure Through Log Files */
|
||||
m.add_cwe (532);
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_exposure_through_output_file,
|
||||
"sensitive value %qE written to output file",
|
||||
m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_sensitive)
|
||||
{
|
||||
m_first_sensitive_event = change.m_event_id;
|
||||
return change.formatted_print ("sensitive value acquired here");
|
||||
}
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_call_with_state (const evdesc::call_with_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_sensitive)
|
||||
return info.formatted_print
|
||||
("passing sensitive value %qE in call to %qE from %qE",
|
||||
info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_return_of_state (const evdesc::return_of_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_sensitive)
|
||||
return info.formatted_print ("returning sensitive value to %qE from %qE",
|
||||
info.m_caller_fndecl, info.m_callee_fndecl);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_first_sensitive_event.known_p ())
|
||||
return ev.formatted_print ("sensitive value %qE written to output file"
|
||||
"; acquired at %@",
|
||||
m_arg, &m_first_sensitive_event);
|
||||
else
|
||||
return ev.formatted_print ("sensitive value %qE written to output file",
|
||||
m_arg);
|
||||
}
|
||||
|
||||
private:
|
||||
const sensitive_state_machine &m_sm;
|
||||
tree m_arg;
|
||||
diagnostic_event_id_t m_first_sensitive_event;
|
||||
};
|
||||
|
||||
/* sensitive_state_machine's ctor. */
|
||||
|
||||
sensitive_state_machine::sensitive_state_machine (logger *logger)
|
||||
: state_machine ("sensitive", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
m_sensitive = add_state ("sensitive");
|
||||
m_stop = add_state ("stop");
|
||||
}
|
||||
|
||||
/* Warn about an exposure at NODE and STMT if ARG is in the "sensitive"
|
||||
state. */
|
||||
|
||||
void
|
||||
sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree arg) const
|
||||
{
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
|
||||
new exposure_through_output_file (*this, arg));
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for
|
||||
sensitive_state_machine. */
|
||||
|
||||
bool
|
||||
sensitive_state_machine::on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
{
|
||||
if (is_named_call_p (callee_fndecl, "getpass", call, 1))
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_sensitive);
|
||||
return true;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "fprintf")
|
||||
|| is_named_call_p (callee_fndecl, "printf"))
|
||||
{
|
||||
/* Handle a match at any position in varargs. */
|
||||
for (unsigned idx = 1; idx < gimple_call_num_args (call); idx++)
|
||||
{
|
||||
tree arg = gimple_call_arg (call, idx);
|
||||
warn_for_any_exposure (sm_ctxt, node, stmt, arg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "fwrite", call, 4))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
warn_for_any_exposure (sm_ctxt, node, stmt, arg);
|
||||
return true;
|
||||
}
|
||||
// TODO: ...etc. This is just a proof-of-concept at this point.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
sensitive_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
|
||||
const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt ATTRIBUTE_UNUSED,
|
||||
tree lhs ATTRIBUTE_UNUSED,
|
||||
enum tree_code op ATTRIBUTE_UNUSED,
|
||||
tree rhs ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
/* Empty. */
|
||||
}
|
||||
|
||||
bool
|
||||
sensitive_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_sensitive_state_machine (logger *logger)
|
||||
{
|
||||
return new sensitive_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
328
gcc/analyzer/sm-signal.cc
Normal file
328
gcc/analyzer/sm-signal.cc
Normal file
|
@ -0,0 +1,328 @@
|
|||
/* An experimental state machine, for tracking bad calls from within
|
||||
signal handlers.
|
||||
|
||||
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 "options.h"
|
||||
#include "bitmap.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "diagnostic-event-id.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.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 "digraph.h"
|
||||
#include "cfg.h"
|
||||
#include "gimple-iterator.h"
|
||||
#include "cgraph.h"
|
||||
#include "analyzer/supergraph.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "alloc-pool.h"
|
||||
#include "fibonacci_heap.h"
|
||||
#include "analyzer/diagnostic-manager.h"
|
||||
#include "shortest-paths.h"
|
||||
#include "analyzer/exploded-graph.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace {
|
||||
|
||||
/* An experimental state machine, for tracking calls to async-signal-unsafe
|
||||
functions from within signal handlers. */
|
||||
|
||||
class signal_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
signal_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
|
||||
/* These states are "global", rather than per-expression. */
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
/* State for when we're in a signal handler. */
|
||||
state_t m_in_signal_handler;
|
||||
|
||||
/* Stop state. */
|
||||
state_t m_stop;
|
||||
};
|
||||
|
||||
/* Concrete subclass for describing call to an async-signal-unsafe function
|
||||
from a signal handler. */
|
||||
|
||||
class signal_unsafe_call
|
||||
: public pending_diagnostic_subclass<signal_unsafe_call>
|
||||
{
|
||||
public:
|
||||
signal_unsafe_call (const signal_state_machine &sm, const gcall *unsafe_call,
|
||||
tree unsafe_fndecl)
|
||||
: m_sm (sm), m_unsafe_call (unsafe_call), m_unsafe_fndecl (unsafe_fndecl)
|
||||
{
|
||||
gcc_assert (m_unsafe_fndecl);
|
||||
}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "signal_unsafe_call"; }
|
||||
|
||||
bool operator== (const signal_unsafe_call &other) const
|
||||
{
|
||||
return m_unsafe_call == other.m_unsafe_call;
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
/* CWE-479: Signal Handler Use of a Non-reentrant Function. */
|
||||
m.add_cwe (479);
|
||||
return warning_at (rich_loc, m,
|
||||
OPT_Wanalyzer_unsafe_call_within_signal_handler,
|
||||
"call to %qD from within signal handler",
|
||||
m_unsafe_fndecl);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.is_global_p ()
|
||||
&& change.m_new_state == m_sm.m_in_signal_handler)
|
||||
{
|
||||
function *handler
|
||||
= change.m_event.m_dst_state.m_region_model->get_current_function ();
|
||||
return change.formatted_print ("registering %qD as signal handler",
|
||||
handler->decl);
|
||||
}
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("call to %qD from within signal handler",
|
||||
m_unsafe_fndecl);
|
||||
}
|
||||
|
||||
private:
|
||||
const signal_state_machine &m_sm;
|
||||
const gcall *m_unsafe_call;
|
||||
tree m_unsafe_fndecl;
|
||||
};
|
||||
|
||||
/* signal_state_machine's ctor. */
|
||||
|
||||
signal_state_machine::signal_state_machine (logger *logger)
|
||||
: state_machine ("signal", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
m_in_signal_handler = add_state ("in_signal_handler");
|
||||
m_stop = add_state ("stop");
|
||||
}
|
||||
|
||||
/* Update MODEL for edges that simulate HANDLER_FUN being called as
|
||||
an signal-handler in response to a signal. */
|
||||
|
||||
static void
|
||||
update_model_for_signal_handler (region_model *model,
|
||||
function *handler_fun)
|
||||
{
|
||||
/* Purge all state within MODEL. */
|
||||
*model = region_model ();
|
||||
model->push_frame (handler_fun, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Custom exploded_edge info: entry into a signal-handler. */
|
||||
|
||||
class signal_delivery_edge_info_t : public exploded_edge::custom_info_t
|
||||
{
|
||||
public:
|
||||
void print (pretty_printer *pp) FINAL OVERRIDE
|
||||
{
|
||||
pp_string (pp, "signal delivered");
|
||||
}
|
||||
|
||||
void update_model (region_model *model,
|
||||
const exploded_edge &eedge) FINAL OVERRIDE
|
||||
{
|
||||
update_model_for_signal_handler (model, eedge.m_dest->get_function ());
|
||||
}
|
||||
|
||||
void add_events_to_path (checker_path *emission_path,
|
||||
const exploded_edge &eedge ATTRIBUTE_UNUSED)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
emission_path->add_event
|
||||
(new custom_event (UNKNOWN_LOCATION, NULL_TREE, 0,
|
||||
"later on,"
|
||||
" when the signal is delivered to the process"));
|
||||
}
|
||||
};
|
||||
|
||||
/* Concrete subclass of custom_transition for modeling registration of a
|
||||
signal handler and the signal handler later being called. */
|
||||
|
||||
class register_signal_handler : public custom_transition
|
||||
{
|
||||
public:
|
||||
register_signal_handler (const signal_state_machine &sm,
|
||||
tree fndecl)
|
||||
: m_sm (sm), m_fndecl (fndecl) {}
|
||||
|
||||
/* Model a signal-handler FNDECL being called at some later point
|
||||
by injecting an edge to a new function-entry node with an empty
|
||||
callstring, setting the 'in-signal-handler' global state
|
||||
on the node. */
|
||||
void impl_transition (exploded_graph *eg,
|
||||
exploded_node *src_enode,
|
||||
int sm_idx) FINAL OVERRIDE
|
||||
{
|
||||
function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
|
||||
if (!handler_fun)
|
||||
return;
|
||||
program_point entering_handler
|
||||
= program_point::from_function_entry (eg->get_supergraph (),
|
||||
handler_fun);
|
||||
|
||||
program_state state_entering_handler (eg->get_ext_state ());
|
||||
update_model_for_signal_handler (state_entering_handler.m_region_model,
|
||||
handler_fun);
|
||||
state_entering_handler.m_checker_states[sm_idx]->set_global_state
|
||||
(m_sm.m_in_signal_handler);
|
||||
|
||||
exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
|
||||
state_entering_handler,
|
||||
NULL);
|
||||
if (dst_enode)
|
||||
eg->add_edge (src_enode, dst_enode, NULL, state_change (),
|
||||
new signal_delivery_edge_info_t ());
|
||||
}
|
||||
|
||||
const signal_state_machine &m_sm;
|
||||
tree m_fndecl;
|
||||
};
|
||||
|
||||
/* Return true if CALL is known to be unsafe to call from a signal handler. */
|
||||
|
||||
static bool
|
||||
signal_unsafe_p (tree callee_fndecl)
|
||||
{
|
||||
// TODO: maintain a list of known unsafe functions
|
||||
if (is_named_call_p (callee_fndecl, "fprintf"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for signal_state_machine. */
|
||||
|
||||
bool
|
||||
signal_state_machine::on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
const state_t global_state = sm_ctxt->get_global_state ();
|
||||
if (global_state == m_start)
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
if (is_named_call_p (callee_fndecl, "signal", call, 2))
|
||||
{
|
||||
tree handler = gimple_call_arg (call, 1);
|
||||
if (TREE_CODE (handler) == ADDR_EXPR
|
||||
&& TREE_CODE (TREE_OPERAND (handler, 0)) == FUNCTION_DECL)
|
||||
{
|
||||
tree fndecl = TREE_OPERAND (handler, 0);
|
||||
register_signal_handler rsh (*this, fndecl);
|
||||
sm_ctxt->on_custom_transition (&rsh);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (global_state == m_in_signal_handler)
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
if (signal_unsafe_p (callee_fndecl))
|
||||
sm_ctxt->warn_for_state (node, stmt, NULL_TREE, m_in_signal_handler,
|
||||
new signal_unsafe_call (*this, call,
|
||||
callee_fndecl));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for
|
||||
signal_state_machine. */
|
||||
|
||||
void
|
||||
signal_state_machine::on_condition (sm_context *sm_ctxt ATTRIBUTE_UNUSED,
|
||||
const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt ATTRIBUTE_UNUSED,
|
||||
tree lhs ATTRIBUTE_UNUSED,
|
||||
enum tree_code op ATTRIBUTE_UNUSED,
|
||||
tree rhs ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
|
||||
bool
|
||||
signal_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_signal_state_machine (logger *logger)
|
||||
{
|
||||
return new signal_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
328
gcc/analyzer/sm-taint.cc
Normal file
328
gcc/analyzer/sm-taint.cc
Normal file
|
@ -0,0 +1,328 @@
|
|||
/* An experimental state machine, for tracking "taint": unsanitized uses
|
||||
of data potentially under an attacker's control.
|
||||
|
||||
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 "options.h"
|
||||
#include "diagnostic-path.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "diagnostic-event-id.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace {
|
||||
|
||||
/* An experimental state machine, for tracking "taint": unsanitized uses
|
||||
of data potentially under an attacker's control. */
|
||||
|
||||
class taint_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
taint_state_machine (logger *logger);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return true; }
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
||||
void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs) const FINAL OVERRIDE;
|
||||
|
||||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
/* State for a "tainted" value: unsanitized data potentially under an
|
||||
attacker's control. */
|
||||
state_t m_tainted;
|
||||
|
||||
/* State for a "tainted" value that has a lower bound. */
|
||||
state_t m_has_lb;
|
||||
|
||||
/* State for a "tainted" value that has an upper bound. */
|
||||
state_t m_has_ub;
|
||||
|
||||
/* Stop state, for a value we don't want to track any more. */
|
||||
state_t m_stop;
|
||||
};
|
||||
|
||||
enum bounds
|
||||
{
|
||||
BOUNDS_NONE,
|
||||
BOUNDS_UPPER,
|
||||
BOUNDS_LOWER
|
||||
};
|
||||
|
||||
class tainted_array_index
|
||||
: public pending_diagnostic_subclass<tainted_array_index>
|
||||
{
|
||||
public:
|
||||
tainted_array_index (const taint_state_machine &sm, tree arg,
|
||||
enum bounds has_bounds)
|
||||
: m_sm (sm), m_arg (arg), m_has_bounds (has_bounds) {}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
|
||||
|
||||
bool operator== (const tainted_array_index &other) const
|
||||
{
|
||||
return m_arg == other.m_arg;
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (129);
|
||||
switch (m_has_bounds)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case BOUNDS_NONE:
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
|
||||
"use of tainted value %qE in array lookup"
|
||||
" without bounds checking",
|
||||
m_arg);
|
||||
break;
|
||||
case BOUNDS_UPPER:
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
|
||||
"use of tainted value %qE in array lookup"
|
||||
" without lower-bounds checking",
|
||||
m_arg);
|
||||
break;
|
||||
case BOUNDS_LOWER:
|
||||
return warning_at (rich_loc, m, OPT_Wanalyzer_tainted_array_index,
|
||||
"use of tainted value %qE in array lookup"
|
||||
" without upper-bounds checking",
|
||||
m_arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_tainted)
|
||||
{
|
||||
if (change.m_origin)
|
||||
return change.formatted_print ("%qE has an unchecked value here"
|
||||
" (from %qE)",
|
||||
change.m_expr, change.m_origin);
|
||||
else
|
||||
return change.formatted_print ("%qE gets an unchecked value here",
|
||||
change.m_expr);
|
||||
}
|
||||
else if (change.m_new_state == m_sm.m_has_lb)
|
||||
return change.formatted_print ("%qE has its lower bound checked here",
|
||||
change.m_expr);
|
||||
else if (change.m_new_state == m_sm.m_has_ub)
|
||||
return change.formatted_print ("%qE has its upper bound checked here",
|
||||
change.m_expr);
|
||||
return label_text ();
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
switch (m_has_bounds)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case BOUNDS_NONE:
|
||||
return ev.formatted_print ("use of tainted value %qE in array lookup"
|
||||
" without bounds checking",
|
||||
m_arg);
|
||||
case BOUNDS_UPPER:
|
||||
return ev.formatted_print ("use of tainted value %qE in array lookup"
|
||||
" without lower-bounds checking",
|
||||
m_arg);
|
||||
case BOUNDS_LOWER:
|
||||
return ev.formatted_print ("use of tainted value %qE in array lookup"
|
||||
" without upper-bounds checking",
|
||||
m_arg);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const taint_state_machine &m_sm;
|
||||
tree m_arg;
|
||||
enum bounds m_has_bounds;
|
||||
};
|
||||
|
||||
/* taint_state_machine's ctor. */
|
||||
|
||||
taint_state_machine::taint_state_machine (logger *logger)
|
||||
: state_machine ("taint", logger)
|
||||
{
|
||||
m_start = add_state ("start");
|
||||
m_tainted = add_state ("tainted");
|
||||
m_has_lb = add_state ("has_lb");
|
||||
m_has_ub = add_state ("has_ub");
|
||||
m_stop = add_state ("stop");
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for taint_state_machine. */
|
||||
|
||||
bool
|
||||
taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
if (const gcall *call = dyn_cast <const gcall *> (stmt))
|
||||
if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
|
||||
{
|
||||
if (is_named_call_p (callee_fndecl, "fread", call, 4))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
|
||||
|
||||
/* Dereference an ADDR_EXPR. */
|
||||
// TODO: should the engine do this?
|
||||
if (TREE_CODE (arg) == ADDR_EXPR)
|
||||
sm_ctxt->on_transition (node, stmt, TREE_OPERAND (arg, 0),
|
||||
m_start, m_tainted);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO: ...etc; many other sources of untrusted data
|
||||
|
||||
if (const gassign *assign = dyn_cast <const gassign *> (stmt))
|
||||
{
|
||||
tree rhs1 = gimple_assign_rhs1 (assign);
|
||||
enum tree_code op = gimple_assign_rhs_code (assign);
|
||||
|
||||
/* Check array accesses. */
|
||||
if (op == ARRAY_REF)
|
||||
{
|
||||
tree arg = TREE_OPERAND (rhs1, 1);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
/* Unsigned types have an implicit lower bound. */
|
||||
bool is_unsigned = false;
|
||||
if (INTEGRAL_TYPE_P (TREE_TYPE (arg)))
|
||||
is_unsigned = TYPE_UNSIGNED (TREE_TYPE (arg));
|
||||
|
||||
/* Complain about missing bounds. */
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_tainted,
|
||||
new tainted_array_index (*this, arg,
|
||||
is_unsigned
|
||||
? BOUNDS_LOWER : BOUNDS_NONE));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
|
||||
|
||||
/* Complain about missing upper bound. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_has_lb,
|
||||
new tainted_array_index (*this, arg,
|
||||
BOUNDS_LOWER));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_has_lb, m_stop);
|
||||
|
||||
/* Complain about missing lower bound. */
|
||||
if (!is_unsigned)
|
||||
{
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_has_ub,
|
||||
new tainted_array_index (*this, arg,
|
||||
BOUNDS_UPPER));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_has_ub, m_stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_condition vfunc for taint_state_machine.
|
||||
Potentially transition state 'tainted' to 'has_ub' or 'has_lb',
|
||||
and states 'has_ub' and 'has_lb' to 'stop'. */
|
||||
|
||||
void
|
||||
taint_state_machine::on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
tree rhs ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
if (stmt == NULL)
|
||||
return;
|
||||
|
||||
// TODO: this doesn't use the RHS; should we make it symmetric?
|
||||
|
||||
// TODO
|
||||
switch (op)
|
||||
{
|
||||
//case NE_EXPR:
|
||||
//case EQ_EXPR:
|
||||
case GE_EXPR:
|
||||
case GT_EXPR:
|
||||
{
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_tainted,
|
||||
m_has_lb);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_has_ub,
|
||||
m_stop);
|
||||
}
|
||||
break;
|
||||
case LE_EXPR:
|
||||
case LT_EXPR:
|
||||
{
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_tainted,
|
||||
m_has_ub);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_has_lb,
|
||||
m_stop);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
taint_state_machine::can_purge_p (state_t s ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* Internal interface to this file. */
|
||||
|
||||
state_machine *
|
||||
make_taint_state_machine (logger *logger)
|
||||
{
|
||||
return new taint_state_machine (logger);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
122
gcc/analyzer/sm.cc
Normal file
122
gcc/analyzer/sm.cc
Normal file
|
@ -0,0 +1,122 @@
|
|||
/* Modeling API uses and misuses via state machines.
|
||||
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 "options.h"
|
||||
#include "function.h"
|
||||
#include "diagnostic-core.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/sm.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
/* If STMT is an assignment from zero, return the LHS. */
|
||||
|
||||
tree
|
||||
is_zero_assignment (const gimple *stmt)
|
||||
{
|
||||
const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
|
||||
if (!assign_stmt)
|
||||
return NULL_TREE;
|
||||
|
||||
enum tree_code op = gimple_assign_rhs_code (assign_stmt);
|
||||
if (TREE_CODE_CLASS (op) != tcc_constant)
|
||||
return NULL_TREE;
|
||||
|
||||
if (!zerop (gimple_assign_rhs1 (assign_stmt)))
|
||||
return NULL_TREE;
|
||||
|
||||
return gimple_assign_lhs (assign_stmt);
|
||||
}
|
||||
|
||||
/* Return true if VAR has pointer or reference type. */
|
||||
|
||||
bool
|
||||
any_pointer_p (tree var)
|
||||
{
|
||||
return POINTER_TYPE_P (TREE_TYPE (var));
|
||||
}
|
||||
|
||||
/* Add a state with name NAME to this state_machine.
|
||||
The string is required to outlive the state_machine.
|
||||
|
||||
Return the state_t for the new state. */
|
||||
|
||||
state_machine::state_t
|
||||
state_machine::add_state (const char *name)
|
||||
{
|
||||
m_state_names.safe_push (name);
|
||||
return m_state_names.length () - 1;
|
||||
}
|
||||
|
||||
/* Get the name of state S within this state_machine. */
|
||||
|
||||
const char *
|
||||
state_machine::get_state_name (state_t s) const
|
||||
{
|
||||
return m_state_names[s];
|
||||
}
|
||||
|
||||
/* Assert that S is a valid state for this state_machine. */
|
||||
|
||||
void
|
||||
state_machine::validate (state_t s) const
|
||||
{
|
||||
gcc_assert (s < m_state_names.length ());
|
||||
}
|
||||
|
||||
/* Create instances of the various state machines, each using LOGGER,
|
||||
and populate OUT with them. */
|
||||
|
||||
void
|
||||
make_checkers (auto_delete_vec <state_machine> &out, logger *logger)
|
||||
{
|
||||
out.safe_push (make_malloc_state_machine (logger));
|
||||
out.safe_push (make_fileptr_state_machine (logger));
|
||||
out.safe_push (make_taint_state_machine (logger));
|
||||
out.safe_push (make_sensitive_state_machine (logger));
|
||||
out.safe_push (make_signal_state_machine (logger));
|
||||
|
||||
/* We only attempt to run the pattern tests if it might have been manually
|
||||
enabled (for DejaGnu purposes). */
|
||||
if (flag_analyzer_checker)
|
||||
out.safe_push (make_pattern_test_state_machine (logger));
|
||||
|
||||
if (flag_analyzer_checker)
|
||||
{
|
||||
unsigned read_index, write_index;
|
||||
state_machine **sm;
|
||||
|
||||
/* TODO: this leaks the machines
|
||||
Would be nice to log the things that were removed. */
|
||||
VEC_ORDERED_REMOVE_IF (out, read_index, write_index, sm,
|
||||
0 != strcmp (flag_analyzer_checker,
|
||||
(*sm)->get_name ()));
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
178
gcc/analyzer/sm.h
Normal file
178
gcc/analyzer/sm.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
/* Modeling API uses and misuses via state machines.
|
||||
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_SM_H
|
||||
#define GCC_ANALYZER_SM_H
|
||||
|
||||
/* Utility functions for use by state machines. */
|
||||
|
||||
extern tree is_zero_assignment (const gimple *stmt);
|
||||
extern bool any_pointer_p (tree var);
|
||||
|
||||
class state_machine;
|
||||
class sm_context;
|
||||
class pending_diagnostic;
|
||||
|
||||
/* An abstract base class for a state machine describing an API.
|
||||
A mapping from state IDs to names, and various virtual functions
|
||||
for pattern-matching on statements. */
|
||||
|
||||
class state_machine : public log_user
|
||||
{
|
||||
public:
|
||||
typedef unsigned state_t;
|
||||
|
||||
state_machine (const char *name, logger *logger)
|
||||
: log_user (logger), m_name (name) {}
|
||||
|
||||
virtual ~state_machine () {}
|
||||
|
||||
/* Should states be inherited from a parent region to a child region,
|
||||
when first accessing a child region?
|
||||
For example we should inherit the taintedness of a subregion,
|
||||
but we should not inherit the "malloc:non-null" state of a field
|
||||
within a heap-allocated struct. */
|
||||
virtual bool inherited_state_p () const = 0;
|
||||
|
||||
const char *get_name () const { return m_name; }
|
||||
|
||||
const char *get_state_name (state_t s) const;
|
||||
|
||||
/* Return true if STMT is a function call recognized by this sm. */
|
||||
virtual bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const = 0;
|
||||
|
||||
virtual void on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs, enum tree_code op, tree rhs) const = 0;
|
||||
|
||||
/* Return true if it safe to discard the given state (to help
|
||||
when simplifying state objects).
|
||||
States that need leak detection should return false. */
|
||||
virtual bool can_purge_p (state_t s) const = 0;
|
||||
|
||||
/* Called when VAR leaks (and !can_purge_p). */
|
||||
virtual pending_diagnostic *on_leak (tree var ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void validate (state_t s) const;
|
||||
|
||||
protected:
|
||||
state_t add_state (const char *name);
|
||||
|
||||
private:
|
||||
DISABLE_COPY_AND_ASSIGN (state_machine);
|
||||
|
||||
const char *m_name;
|
||||
auto_vec<const char *> m_state_names;
|
||||
};
|
||||
|
||||
/* Is STATE the start state? (zero is hardcoded as the start state). */
|
||||
|
||||
static inline bool
|
||||
start_start_p (state_machine::state_t state)
|
||||
{
|
||||
return state == 0;
|
||||
}
|
||||
|
||||
/* Abstract base class for state machines to pass to
|
||||
sm_context::on_custom_transition for handling non-standard transitions
|
||||
(e.g. adding a node and edge to simulate registering a callback and having
|
||||
the callback be called later). */
|
||||
|
||||
class custom_transition
|
||||
{
|
||||
public:
|
||||
virtual ~custom_transition () {}
|
||||
virtual void impl_transition (exploded_graph *eg,
|
||||
exploded_node *src_enode,
|
||||
int sm_idx) = 0;
|
||||
};
|
||||
|
||||
/* Abstract base class giving an interface for the state machine to call
|
||||
the checker engine, at a particular stmt. */
|
||||
|
||||
class sm_context
|
||||
{
|
||||
public:
|
||||
virtual ~sm_context () {}
|
||||
|
||||
/* Get the fndecl used at call, or NULL_TREE.
|
||||
Use in preference to gimple_call_fndecl (and gimple_call_addr_fndecl),
|
||||
since it can look through function pointer assignments and
|
||||
other callback handling. */
|
||||
virtual tree get_fndecl_for_call (const gcall *call) = 0;
|
||||
|
||||
/* Called by state_machine in response to pattern matches:
|
||||
if VAR is in state FROM, transition it to state TO, potentially
|
||||
recording the "origin" of the state as ORIGIN.
|
||||
Use NODE and STMT for location information. */
|
||||
virtual void on_transition (const supernode *node, const gimple *stmt,
|
||||
tree var,
|
||||
state_machine::state_t from,
|
||||
state_machine::state_t to,
|
||||
tree origin = NULL_TREE) = 0;
|
||||
|
||||
/* Called by state_machine in response to pattern matches:
|
||||
issue a diagnostic D if VAR is in state STATE, using NODE and STMT
|
||||
for location information. */
|
||||
virtual void warn_for_state (const supernode *node, const gimple *stmt,
|
||||
tree var, state_machine::state_t state,
|
||||
pending_diagnostic *d) = 0;
|
||||
|
||||
virtual tree get_readable_tree (tree expr)
|
||||
{
|
||||
return expr;
|
||||
}
|
||||
|
||||
virtual state_machine::state_t get_global_state () const = 0;
|
||||
virtual void set_global_state (state_machine::state_t) = 0;
|
||||
|
||||
/* A vfunc for handling custom transitions, such as when registering
|
||||
a signal handler. */
|
||||
virtual void on_custom_transition (custom_transition *transition) = 0;
|
||||
|
||||
protected:
|
||||
sm_context (int sm_idx, const state_machine &sm)
|
||||
: m_sm_idx (sm_idx), m_sm (sm) {}
|
||||
|
||||
int m_sm_idx;
|
||||
const state_machine &m_sm;
|
||||
};
|
||||
|
||||
|
||||
/* The various state_machine subclasses are hidden in their respective
|
||||
implementation files. */
|
||||
|
||||
extern void make_checkers (auto_delete_vec <state_machine> &out,
|
||||
logger *logger);
|
||||
|
||||
extern state_machine *make_malloc_state_machine (logger *logger);
|
||||
extern state_machine *make_fileptr_state_machine (logger *logger);
|
||||
extern state_machine *make_taint_state_machine (logger *logger);
|
||||
extern state_machine *make_sensitive_state_machine (logger *logger);
|
||||
extern state_machine *make_signal_state_machine (logger *logger);
|
||||
extern state_machine *make_pattern_test_state_machine (logger *logger);
|
||||
|
||||
#endif /* GCC_ANALYZER_SM_H */
|
534
gcc/analyzer/state-purge.cc
Normal file
534
gcc/analyzer/state-purge.cc
Normal file
|
@ -0,0 +1,534 @@
|
|||
/* Classes for purging state at function_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 "tree.h"
|
||||
#include "timevar.h"
|
||||
#include "tree-ssa-alias.h"
|
||||
#include "function.h"
|
||||
#include "basic-block.h"
|
||||
#include "gimple.h"
|
||||
#include "stringpool.h"
|
||||
#include "tree-vrp.h"
|
||||
#include "gimple-ssa.h"
|
||||
#include "tree-ssanames.h"
|
||||
#include "tree-phinodes.h"
|
||||
#include "options.h"
|
||||
#include "ssa-iterators.h"
|
||||
#include "gimple-pretty-print.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "digraph.h"
|
||||
#include "ordered-hash-map.h"
|
||||
#include "cfg.h"
|
||||
#include "gimple-iterator.h"
|
||||
#include "cgraph.h"
|
||||
#include "analyzer/supergraph.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "analyzer/state-purge.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
/* state_purge_map's ctor. Walk all SSA names in all functions, building
|
||||
a state_purge_per_ssa_name instance for each. */
|
||||
|
||||
state_purge_map::state_purge_map (const supergraph &sg,
|
||||
logger *logger)
|
||||
: log_user (logger), m_sg (sg)
|
||||
{
|
||||
LOG_FUNC (logger);
|
||||
|
||||
auto_timevar tv (TV_ANALYZER_STATE_PURGE);
|
||||
|
||||
cgraph_node *node;
|
||||
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
|
||||
{
|
||||
function *fun = node->get_fun ();
|
||||
if (logger)
|
||||
log ("function: %s", function_name (fun));
|
||||
tree name;
|
||||
unsigned int i;;
|
||||
FOR_EACH_SSA_NAME (i, name, fun)
|
||||
{
|
||||
/* For now, don't bother tracking the .MEM SSA names. */
|
||||
if (tree var = SSA_NAME_VAR (name))
|
||||
if (TREE_CODE (var) == VAR_DECL)
|
||||
if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
|
||||
continue;
|
||||
m_map.put (name, new state_purge_per_ssa_name (*this, name, fun));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* state_purge_map's dtor. */
|
||||
|
||||
state_purge_map::~state_purge_map ()
|
||||
{
|
||||
for (iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
|
||||
delete (*iter).second;
|
||||
}
|
||||
|
||||
/* state_purge_per_ssa_name's ctor.
|
||||
|
||||
Locate all uses of VAR within FUN.
|
||||
Walk backwards from each use, marking program points, until
|
||||
we reach the def stmt, populating m_points_needing_var.
|
||||
|
||||
We have to track program points rather than
|
||||
just stmts since there could be empty basic blocks on the way. */
|
||||
|
||||
state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map,
|
||||
tree name,
|
||||
function *fun)
|
||||
: m_points_needing_name (), m_name (name), m_fun (fun)
|
||||
{
|
||||
LOG_FUNC (map.get_logger ());
|
||||
|
||||
if (map.get_logger ())
|
||||
{
|
||||
map.log ("SSA name: %qE within %qD", name, fun->decl);
|
||||
|
||||
/* Show def stmt. */
|
||||
const gimple *def_stmt = SSA_NAME_DEF_STMT (name);
|
||||
pretty_printer pp;
|
||||
pp_gimple_stmt_1 (&pp, def_stmt, 0, (dump_flags_t)0);
|
||||
map.log ("def stmt: %s", pp_formatted_text (&pp));
|
||||
}
|
||||
|
||||
auto_vec<function_point> worklist;
|
||||
|
||||
/* Add all immediate uses of name to the worklist.
|
||||
Compare with debug_immediate_uses. */
|
||||
imm_use_iterator iter;
|
||||
use_operand_p use_p;
|
||||
FOR_EACH_IMM_USE_FAST (use_p, iter, name)
|
||||
{
|
||||
if (USE_STMT (use_p))
|
||||
{
|
||||
const gimple *use_stmt = USE_STMT (use_p);
|
||||
if (map.get_logger ())
|
||||
{
|
||||
pretty_printer pp;
|
||||
pp_gimple_stmt_1 (&pp, use_stmt, 0, (dump_flags_t)0);
|
||||
map.log ("used by stmt: %s", pp_formatted_text (&pp));
|
||||
}
|
||||
|
||||
const supernode *snode
|
||||
= map.get_sg ().get_supernode_for_stmt (use_stmt);
|
||||
|
||||
/* If it's a use within a phi node, then we care about
|
||||
which in-edge we came from. */
|
||||
if (use_stmt->code == GIMPLE_PHI)
|
||||
{
|
||||
for (gphi_iterator gpi
|
||||
= const_cast<supernode *> (snode)->start_phis ();
|
||||
!gsi_end_p (gpi); gsi_next (&gpi))
|
||||
{
|
||||
gphi *phi = gpi.phi ();
|
||||
if (phi == use_stmt)
|
||||
{
|
||||
/* Find arguments (and thus in-edges) which use NAME. */
|
||||
for (unsigned arg_idx = 0;
|
||||
arg_idx < gimple_phi_num_args (phi);
|
||||
++arg_idx)
|
||||
{
|
||||
if (name == gimple_phi_arg (phi, arg_idx)->def)
|
||||
{
|
||||
edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
|
||||
const superedge *in_sedge
|
||||
= map.get_sg ().get_edge_for_cfg_edge (in_edge);
|
||||
function_point point
|
||||
= function_point::before_supernode
|
||||
(snode, in_sedge);
|
||||
add_to_worklist (point, &worklist,
|
||||
map.get_logger ());
|
||||
m_points_needing_name.add (point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
function_point point = before_use_stmt (map, use_stmt);
|
||||
add_to_worklist (point, &worklist, map.get_logger ());
|
||||
m_points_needing_name.add (point);
|
||||
|
||||
/* We also need to add uses for conditionals and switches,
|
||||
where the stmt "happens" at the after_supernode, for filtering
|
||||
the out-edges. */
|
||||
if (use_stmt == snode->get_last_stmt ())
|
||||
{
|
||||
if (map.get_logger ())
|
||||
map.log ("last stmt in BB");
|
||||
function_point point
|
||||
= function_point::after_supernode (snode);
|
||||
add_to_worklist (point, &worklist, map.get_logger ());
|
||||
m_points_needing_name.add (point);
|
||||
}
|
||||
else
|
||||
if (map.get_logger ())
|
||||
map.log ("not last stmt in BB");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Process worklist by walking backwards until we reach the def stmt. */
|
||||
{
|
||||
log_scope s (map.get_logger (), "processing worklist");
|
||||
while (worklist.length () > 0)
|
||||
{
|
||||
function_point point = worklist.pop ();
|
||||
process_point (point, &worklist, map);
|
||||
}
|
||||
}
|
||||
|
||||
if (map.get_logger ())
|
||||
{
|
||||
map.log ("%qE in %qD is needed to process:", name, fun->decl);
|
||||
for (point_set_t::iterator iter = m_points_needing_name.begin ();
|
||||
iter != m_points_needing_name.end ();
|
||||
++iter)
|
||||
{
|
||||
map.start_log_line ();
|
||||
map.get_logger ()->log_partial (" point: ");
|
||||
(*iter).print (map.get_logger ()->get_printer (), format (false));
|
||||
map.end_log_line ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if the SSA name is needed at POINT. */
|
||||
|
||||
bool
|
||||
state_purge_per_ssa_name::needed_at_point_p (const function_point &point) const
|
||||
{
|
||||
return const_cast <point_set_t &> (m_points_needing_name).contains (point);
|
||||
}
|
||||
|
||||
/* Get the function_point representing immediately before USE_STMT.
|
||||
Subroutine of ctor. */
|
||||
|
||||
function_point
|
||||
state_purge_per_ssa_name::before_use_stmt (const state_purge_map &map,
|
||||
const gimple *use_stmt)
|
||||
{
|
||||
gcc_assert (use_stmt->code != GIMPLE_PHI);
|
||||
|
||||
const supernode *supernode
|
||||
= map.get_sg ().get_supernode_for_stmt (use_stmt);
|
||||
unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
|
||||
return function_point::before_stmt (supernode, stmt_idx);
|
||||
}
|
||||
|
||||
/* Add POINT to *WORKLIST if the point has not already been seen.
|
||||
Subroutine of ctor. */
|
||||
|
||||
void
|
||||
state_purge_per_ssa_name::add_to_worklist (const function_point &point,
|
||||
auto_vec<function_point> *worklist,
|
||||
logger *logger)
|
||||
{
|
||||
LOG_FUNC (logger);
|
||||
if (logger)
|
||||
{
|
||||
logger->start_log_line ();
|
||||
logger->log_partial ("point: '");
|
||||
point.print (logger->get_printer (), format (false));
|
||||
logger->log_partial ("' for worklist for %qE", m_name);
|
||||
logger->end_log_line ();
|
||||
}
|
||||
|
||||
gcc_assert (point.get_function () == m_fun);
|
||||
if (point.get_from_edge ())
|
||||
gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);
|
||||
|
||||
if (m_points_needing_name.contains (point))
|
||||
{
|
||||
if (logger)
|
||||
logger->log ("already seen for %qE", m_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger)
|
||||
logger->log ("not seen; adding to worklist for %qE", m_name);
|
||||
m_points_needing_name.add (point);
|
||||
worklist->safe_push (point);
|
||||
}
|
||||
}
|
||||
|
||||
/* Process POINT, popped from WORKLIST.
|
||||
Iterate over predecessors of POINT, adding to WORKLIST. */
|
||||
|
||||
void
|
||||
state_purge_per_ssa_name::process_point (const function_point &point,
|
||||
auto_vec<function_point> *worklist,
|
||||
const state_purge_map &map)
|
||||
{
|
||||
logger *logger = map.get_logger ();
|
||||
LOG_FUNC (logger);
|
||||
if (logger)
|
||||
{
|
||||
logger->start_log_line ();
|
||||
logger->log_partial ("considering point: '");
|
||||
point.print (logger->get_printer (), format (false));
|
||||
logger->log_partial ("' for %qE", m_name);
|
||||
logger->end_log_line ();
|
||||
}
|
||||
|
||||
gimple *def_stmt = SSA_NAME_DEF_STMT (m_name);
|
||||
|
||||
const supernode *snode = point.get_supernode ();
|
||||
|
||||
switch (point.get_kind ())
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
|
||||
case PK_ORIGIN:
|
||||
break;
|
||||
|
||||
case PK_BEFORE_SUPERNODE:
|
||||
{
|
||||
for (gphi_iterator gpi
|
||||
= const_cast<supernode *> (snode)->start_phis ();
|
||||
!gsi_end_p (gpi); gsi_next (&gpi))
|
||||
{
|
||||
gphi *phi = gpi.phi ();
|
||||
if (phi == def_stmt)
|
||||
{
|
||||
if (logger)
|
||||
logger->log ("def stmt within phis; terminating");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add given pred to worklist. */
|
||||
if (point.get_from_edge ())
|
||||
{
|
||||
gcc_assert (point.get_from_edge ()->m_src);
|
||||
add_to_worklist
|
||||
(function_point::after_supernode (point.get_from_edge ()->m_src),
|
||||
worklist, logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Add any intraprocedually edge for a call. */
|
||||
if (snode->m_returning_call)
|
||||
{
|
||||
cgraph_edge *cedge
|
||||
= supergraph_call_edge (snode->m_fun,
|
||||
snode->m_returning_call);
|
||||
gcc_assert (cedge);
|
||||
superedge *sedge
|
||||
= map.get_sg ().get_intraprocedural_edge_for_call (cedge);
|
||||
gcc_assert (sedge);
|
||||
add_to_worklist
|
||||
(function_point::after_supernode (sedge->m_src),
|
||||
worklist, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PK_BEFORE_STMT:
|
||||
{
|
||||
if (def_stmt == point.get_stmt ())
|
||||
{
|
||||
if (logger)
|
||||
logger->log ("def stmt; terminating");
|
||||
return;
|
||||
}
|
||||
if (point.get_stmt_idx () > 0)
|
||||
add_to_worklist (function_point::before_stmt
|
||||
(snode, point.get_stmt_idx () - 1),
|
||||
worklist, logger);
|
||||
else
|
||||
{
|
||||
/* Add before_supernode to worklist. This captures the in-edge,
|
||||
so we have to do it once per in-edge. */
|
||||
unsigned i;
|
||||
superedge *pred;
|
||||
FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
|
||||
add_to_worklist (function_point::before_supernode (snode,
|
||||
pred),
|
||||
worklist, logger);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PK_AFTER_SUPERNODE:
|
||||
{
|
||||
if (snode->m_stmts.length ())
|
||||
add_to_worklist
|
||||
(function_point::before_stmt (snode,
|
||||
snode->m_stmts.length () - 1),
|
||||
worklist, logger);
|
||||
else
|
||||
{
|
||||
/* Add before_supernode to worklist. This captures the in-edge,
|
||||
so we have to do it once per in-edge. */
|
||||
unsigned i;
|
||||
superedge *pred;
|
||||
FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
|
||||
add_to_worklist (function_point::before_supernode (snode,
|
||||
pred),
|
||||
worklist, logger);
|
||||
/* If it's the initial BB, add it, to ensure that we
|
||||
have "before supernode" for the initial ENTRY block, and don't
|
||||
erroneously purge SSA names for initial values of parameters. */
|
||||
if (snode->entry_p ())
|
||||
{
|
||||
add_to_worklist
|
||||
(function_point::before_supernode (snode, NULL),
|
||||
worklist, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* class state_purge_annotator : public dot_annotator. */
|
||||
|
||||
/* Implementation of dot_annotator::add_node_annotations vfunc for
|
||||
state_purge_annotator.
|
||||
|
||||
Add an additional record showing which names are purged on entry
|
||||
to the supernode N. */
|
||||
|
||||
void
|
||||
state_purge_annotator::add_node_annotations (graphviz_out *gv,
|
||||
const supernode &n) const
|
||||
{
|
||||
if (m_map == NULL)
|
||||
return;
|
||||
|
||||
pretty_printer *pp = gv->get_pp ();
|
||||
|
||||
pp_printf (pp, "annotation_for_node_%i", n.m_index);
|
||||
pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
|
||||
"lightblue");
|
||||
pp_write_text_to_stream (pp);
|
||||
|
||||
// FIXME: passing in a NULL in-edge means we get no hits
|
||||
function_point before_supernode
|
||||
(function_point::before_supernode (&n, NULL));
|
||||
|
||||
for (state_purge_map::iterator iter = m_map->begin ();
|
||||
iter != m_map->end ();
|
||||
++iter)
|
||||
{
|
||||
tree name = (*iter).first;
|
||||
state_purge_per_ssa_name *per_name_data = (*iter).second;
|
||||
if (per_name_data->get_function () == n.m_fun)
|
||||
{
|
||||
PUSH_IGNORE_WFORMAT
|
||||
if (per_name_data->needed_at_point_p (before_supernode))
|
||||
pp_printf (pp, "%qE needed here", name);
|
||||
else
|
||||
pp_printf (pp, "%qE not needed here", name);
|
||||
POP_IGNORE_WFORMAT
|
||||
}
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
pp_string (pp, "\"];\n\n");
|
||||
pp_flush (pp);
|
||||
}
|
||||
|
||||
/* Print V to GV as a comma-separated list in braces within a <TR>,
|
||||
titling it with TITLE.
|
||||
|
||||
Subroutine of state_purge_annotator::add_stmt_annotations. */
|
||||
|
||||
static void
|
||||
print_vec_of_names (graphviz_out *gv, const char *title,
|
||||
const auto_vec<tree> &v)
|
||||
{
|
||||
pretty_printer *pp = gv->get_pp ();
|
||||
tree name;
|
||||
unsigned i;
|
||||
gv->begin_tr ();
|
||||
pp_printf (pp, "%s: {", title);
|
||||
FOR_EACH_VEC_ELT (v, i, name)
|
||||
{
|
||||
if (i > 0)
|
||||
pp_string (pp, ", ");
|
||||
PUSH_IGNORE_WFORMAT
|
||||
pp_printf (pp, "%qE", name);
|
||||
POP_IGNORE_WFORMAT
|
||||
}
|
||||
pp_printf (pp, "}");
|
||||
pp_write_text_as_html_like_dot_to_stream (pp);
|
||||
gv->end_tr ();
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
/* Implementation of dot_annotator::add_stmt_annotations for
|
||||
state_purge_annotator.
|
||||
|
||||
Add text showing which names are purged at STMT. */
|
||||
|
||||
void
|
||||
state_purge_annotator::add_stmt_annotations (graphviz_out *gv,
|
||||
const gimple *stmt) const
|
||||
{
|
||||
if (m_map == NULL)
|
||||
return;
|
||||
|
||||
if (stmt->code == GIMPLE_PHI)
|
||||
return;
|
||||
|
||||
pretty_printer *pp = gv->get_pp ();
|
||||
|
||||
pp_newline (pp);
|
||||
|
||||
const supernode *supernode = m_map->get_sg ().get_supernode_for_stmt (stmt);
|
||||
unsigned int stmt_idx = supernode->get_stmt_index (stmt);
|
||||
function_point before_stmt
|
||||
(function_point::before_stmt (supernode, stmt_idx));
|
||||
|
||||
auto_vec<tree> needed;
|
||||
auto_vec<tree> not_needed;
|
||||
for (state_purge_map::iterator iter = m_map->begin ();
|
||||
iter != m_map->end ();
|
||||
++iter)
|
||||
{
|
||||
tree name = (*iter).first;
|
||||
state_purge_per_ssa_name *per_name_data = (*iter).second;
|
||||
if (per_name_data->get_function () == supernode->m_fun)
|
||||
{
|
||||
if (per_name_data->needed_at_point_p (before_stmt))
|
||||
needed.safe_push (name);
|
||||
else
|
||||
not_needed.safe_push (name);
|
||||
}
|
||||
}
|
||||
|
||||
print_vec_of_names (gv, "needed here", needed);
|
||||
print_vec_of_names (gv, "not needed here", not_needed);
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
162
gcc/analyzer/state-purge.h
Normal file
162
gcc/analyzer/state-purge.h
Normal file
|
@ -0,0 +1,162 @@
|
|||
/* Classes for purging state at function_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_STATE_PURGE_H
|
||||
#define GCC_ANALYZER_STATE_PURGE_H
|
||||
|
||||
/* Hash traits for function_point. */
|
||||
|
||||
template <> struct default_hash_traits<function_point>
|
||||
: public pod_hash_traits<function_point>
|
||||
{
|
||||
static const bool empty_zero_p = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline hashval_t
|
||||
pod_hash_traits<function_point>::hash (value_type v)
|
||||
{
|
||||
return v.hash ();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<function_point>::equal (const value_type &existing,
|
||||
const value_type &candidate)
|
||||
{
|
||||
return existing == candidate;
|
||||
}
|
||||
template <>
|
||||
inline void
|
||||
pod_hash_traits<function_point>::mark_deleted (value_type &v)
|
||||
{
|
||||
v = function_point::deleted ();
|
||||
}
|
||||
template <>
|
||||
inline void
|
||||
pod_hash_traits<function_point>::mark_empty (value_type &v)
|
||||
{
|
||||
v = function_point::empty ();
|
||||
}
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<function_point>::is_deleted (value_type v)
|
||||
{
|
||||
return v.get_kind () == PK_DELETED;
|
||||
}
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<function_point>::is_empty (value_type v)
|
||||
{
|
||||
return v.get_kind () == PK_EMPTY;
|
||||
}
|
||||
|
||||
/* The result of analyzing which SSA names can be purged from state at
|
||||
different points in the program, so that we can simplify program_state
|
||||
objects, in the hope of reducing state-blowup. */
|
||||
|
||||
class state_purge_map : public log_user
|
||||
{
|
||||
public:
|
||||
typedef ordered_hash_map<tree, state_purge_per_ssa_name *> map_t;
|
||||
typedef map_t::iterator iterator;
|
||||
|
||||
state_purge_map (const supergraph &sg, logger *logger);
|
||||
~state_purge_map ();
|
||||
|
||||
const state_purge_per_ssa_name &get_data_for_ssa_name (tree name) const
|
||||
{
|
||||
gcc_assert (TREE_CODE (name) == SSA_NAME);
|
||||
if (tree var = SSA_NAME_VAR (name))
|
||||
if (TREE_CODE (var) == VAR_DECL)
|
||||
gcc_assert (!VAR_DECL_IS_VIRTUAL_OPERAND (var));
|
||||
|
||||
state_purge_per_ssa_name **slot
|
||||
= const_cast <map_t&> (m_map).get (name);
|
||||
return **slot;
|
||||
}
|
||||
|
||||
const supergraph &get_sg () const { return m_sg; }
|
||||
|
||||
iterator begin () const { return m_map.begin (); }
|
||||
iterator end () const { return m_map.end (); }
|
||||
|
||||
private:
|
||||
DISABLE_COPY_AND_ASSIGN (state_purge_map);
|
||||
|
||||
const supergraph &m_sg;
|
||||
map_t m_map;
|
||||
};
|
||||
|
||||
/* The part of a state_purge_map relating to a specific SSA name.
|
||||
|
||||
The result of analyzing a given SSA name, recording which
|
||||
function_points need to retain state information about it to handle
|
||||
their successor states, so that we can simplify program_state objects,
|
||||
in the hope of reducing state-blowup. */
|
||||
|
||||
class state_purge_per_ssa_name
|
||||
{
|
||||
public:
|
||||
state_purge_per_ssa_name (const state_purge_map &map,
|
||||
tree name,
|
||||
function *fun);
|
||||
|
||||
bool needed_at_point_p (const function_point &point) const;
|
||||
|
||||
function *get_function () const { return m_fun; }
|
||||
|
||||
private:
|
||||
static function_point before_use_stmt (const state_purge_map &map,
|
||||
const gimple *use_stmt);
|
||||
|
||||
void add_to_worklist (const function_point &point,
|
||||
auto_vec<function_point> *worklist,
|
||||
logger *logger);
|
||||
|
||||
void process_point (const function_point &point,
|
||||
auto_vec<function_point> *worklist,
|
||||
const state_purge_map &map);
|
||||
|
||||
typedef hash_set<function_point> point_set_t;
|
||||
point_set_t m_points_needing_name;
|
||||
tree m_name;
|
||||
function *m_fun;
|
||||
};
|
||||
|
||||
/* Subclass of dot_annotator for use by -fdump-analyzer-state-purge.
|
||||
Annotate the .dot output with state-purge information. */
|
||||
|
||||
class state_purge_annotator : public dot_annotator
|
||||
{
|
||||
public:
|
||||
state_purge_annotator (const state_purge_map *map) : m_map (map) {}
|
||||
|
||||
void add_node_annotations (graphviz_out *gv, const supernode &n)
|
||||
const FINAL OVERRIDE;
|
||||
|
||||
void add_stmt_annotations (graphviz_out *gv, const gimple *stmt)
|
||||
const FINAL OVERRIDE;
|
||||
|
||||
private:
|
||||
const state_purge_map *m_map;
|
||||
};
|
||||
|
||||
#endif /* GCC_ANALYZER_STATE_PURGE_H */
|
961
gcc/analyzer/supergraph.cc
Normal file
961
gcc/analyzer/supergraph.cc
Normal file
|
@ -0,0 +1,961 @@
|
|||
/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
|
||||
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 "tm.h"
|
||||
#include "toplev.h"
|
||||
#include "hash-table.h"
|
||||
#include "vec.h"
|
||||
#include "ggc.h"
|
||||
#include "basic-block.h"
|
||||
#include "function.h"
|
||||
#include "gimple-fold.h"
|
||||
#include "tree-eh.h"
|
||||
#include "gimple-expr.h"
|
||||
#include "is-a.h"
|
||||
#include "timevar.h"
|
||||
#include "gimple.h"
|
||||
#include "gimple-iterator.h"
|
||||
#include "gimple-pretty-print.h"
|
||||
#include "tree-pretty-print.h"
|
||||
#include "graphviz.h"
|
||||
#include "cgraph.h"
|
||||
#include "tree-dfa.h"
|
||||
#include "cfganal.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 "analyzer/analyzer-logging.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
/* Get the cgraph_edge, but only if there's an underlying function body. */
|
||||
|
||||
cgraph_edge *
|
||||
supergraph_call_edge (function *fun, gimple *stmt)
|
||||
{
|
||||
gcall *call = dyn_cast<gcall *> (stmt);
|
||||
if (!call)
|
||||
return NULL;
|
||||
cgraph_edge *edge = cgraph_node::get (fun->decl)->get_edge (stmt);
|
||||
if (!edge)
|
||||
return NULL;
|
||||
if (!edge->callee)
|
||||
return NULL; /* e.g. for a function pointer. */
|
||||
if (!edge->callee->get_fun ())
|
||||
return NULL;
|
||||
return edge;
|
||||
}
|
||||
|
||||
/* supergraph's ctor. Walk the callgraph, building supernodes for each
|
||||
CFG basic block, splitting the basic blocks at callsites. Join
|
||||
together the supernodes with interprocedural and intraprocedural
|
||||
superedges as appropriate. */
|
||||
|
||||
supergraph::supergraph (logger *logger)
|
||||
{
|
||||
auto_timevar tv (TV_ANALYZER_SUPERGRAPH);
|
||||
|
||||
LOG_FUNC (logger);
|
||||
|
||||
/* First pass: make supernodes. */
|
||||
{
|
||||
/* Sort the cgraph_nodes? */
|
||||
cgraph_node *node;
|
||||
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
|
||||
{
|
||||
function *fun = node->get_fun ();
|
||||
|
||||
/* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
|
||||
the supergraph (by doing it per-function). */
|
||||
auto_cfun sentinel (fun);
|
||||
mark_dfs_back_edges ();
|
||||
|
||||
const int start_idx = m_nodes.length ();
|
||||
|
||||
basic_block bb;
|
||||
FOR_ALL_BB_FN (bb, fun)
|
||||
{
|
||||
/* The initial supernode for the BB gets the phi nodes (if any). */
|
||||
supernode *node_for_stmts = add_node (fun, bb, NULL, phi_nodes (bb));
|
||||
m_bb_to_initial_node.put (bb, node_for_stmts);
|
||||
for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi);
|
||||
gsi_next (&gpi))
|
||||
{
|
||||
gimple *stmt = gsi_stmt (gpi);
|
||||
m_stmt_to_node_t.put (stmt, node_for_stmts);
|
||||
}
|
||||
|
||||
/* Append statements from BB to the current supernode, splitting
|
||||
them into a new supernode at each call site; such call statements
|
||||
appear in both supernodes (representing call and return). */
|
||||
gimple_stmt_iterator gsi;
|
||||
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||||
{
|
||||
gimple *stmt = gsi_stmt (gsi);
|
||||
node_for_stmts->m_stmts.safe_push (stmt);
|
||||
m_stmt_to_node_t.put (stmt, node_for_stmts);
|
||||
if (cgraph_edge *edge = supergraph_call_edge (fun, stmt))
|
||||
{
|
||||
m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts);
|
||||
node_for_stmts = add_node (fun, bb, as_a <gcall *> (stmt), NULL);
|
||||
m_cgraph_edge_to_caller_next_node.put (edge, node_for_stmts);
|
||||
}
|
||||
}
|
||||
|
||||
m_bb_to_final_node.put (bb, node_for_stmts);
|
||||
}
|
||||
|
||||
const unsigned num_snodes = m_nodes.length () - start_idx;
|
||||
m_function_to_num_snodes.put (fun, num_snodes);
|
||||
|
||||
if (logger)
|
||||
{
|
||||
const int end_idx = m_nodes.length () - 1;
|
||||
logger->log ("SN: %i...%i: function %qD",
|
||||
start_idx, end_idx, fun->decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Second pass: make superedges. */
|
||||
{
|
||||
/* Make superedges for CFG edges. */
|
||||
for (bb_to_node_t::iterator iter = m_bb_to_final_node.begin ();
|
||||
iter != m_bb_to_final_node.end ();
|
||||
++iter)
|
||||
{
|
||||
basic_block bb = (*iter).first;
|
||||
supernode *src_supernode = (*iter).second;
|
||||
|
||||
::edge cfg_edge;
|
||||
int idx;
|
||||
if (bb->succs)
|
||||
FOR_EACH_VEC_ELT (*bb->succs, idx, cfg_edge)
|
||||
{
|
||||
basic_block dest_cfg_block = cfg_edge->dest;
|
||||
supernode *dest_supernode
|
||||
= *m_bb_to_initial_node.get (dest_cfg_block);
|
||||
cfg_superedge *cfg_sedge
|
||||
= add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
|
||||
m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make interprocedural superedges for calls. */
|
||||
{
|
||||
for (cgraph_edge_to_node_t::iterator iter
|
||||
= m_cgraph_edge_to_caller_prev_node.begin ();
|
||||
iter != m_cgraph_edge_to_caller_prev_node.end ();
|
||||
++iter)
|
||||
{
|
||||
cgraph_edge *edge = (*iter).first;
|
||||
supernode *caller_prev_supernode = (*iter).second;
|
||||
basic_block callee_cfg_block
|
||||
= ENTRY_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
|
||||
supernode *callee_supernode
|
||||
= *m_bb_to_initial_node.get (callee_cfg_block);
|
||||
call_superedge *sedge
|
||||
= add_call_superedge (caller_prev_supernode,
|
||||
callee_supernode,
|
||||
edge);
|
||||
m_cgraph_edge_to_call_superedge.put (edge, sedge);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make interprocedural superedges for returns. */
|
||||
{
|
||||
for (cgraph_edge_to_node_t::iterator iter
|
||||
= m_cgraph_edge_to_caller_next_node.begin ();
|
||||
iter != m_cgraph_edge_to_caller_next_node.end ();
|
||||
++iter)
|
||||
{
|
||||
cgraph_edge *edge = (*iter).first;
|
||||
supernode *caller_next_supernode = (*iter).second;
|
||||
basic_block callee_cfg_block
|
||||
= EXIT_BLOCK_PTR_FOR_FN (edge->callee->get_fun ());
|
||||
supernode *callee_supernode
|
||||
= *m_bb_to_initial_node.get (callee_cfg_block);
|
||||
return_superedge *sedge
|
||||
= add_return_superedge (callee_supernode,
|
||||
caller_next_supernode,
|
||||
edge);
|
||||
m_cgraph_edge_to_return_superedge.put (edge, sedge);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make intraprocedural superedges linking the two halves of a call. */
|
||||
{
|
||||
for (cgraph_edge_to_node_t::iterator iter
|
||||
= m_cgraph_edge_to_caller_prev_node.begin ();
|
||||
iter != m_cgraph_edge_to_caller_prev_node.end ();
|
||||
++iter)
|
||||
{
|
||||
cgraph_edge *edge = (*iter).first;
|
||||
supernode *caller_prev_supernode = (*iter).second;
|
||||
supernode *caller_next_supernode
|
||||
= *m_cgraph_edge_to_caller_next_node.get (edge);
|
||||
superedge *sedge
|
||||
= new callgraph_superedge (caller_prev_supernode,
|
||||
caller_next_supernode,
|
||||
SUPEREDGE_INTRAPROCEDURAL_CALL,
|
||||
edge);
|
||||
add_edge (sedge);
|
||||
m_cgraph_edge_to_intraproc_superedge.put (edge, sedge);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump this graph in .dot format to PP, using DUMP_ARGS.
|
||||
Cluster the supernodes by function, then by BB from original CFG. */
|
||||
|
||||
void
|
||||
supergraph::dump_dot_to_pp (pretty_printer *pp,
|
||||
const dump_args_t &dump_args) const
|
||||
{
|
||||
graphviz_out gv (pp);
|
||||
|
||||
pp_string (pp, "digraph \"");
|
||||
pp_write_text_to_stream (pp);
|
||||
pp_string (pp, "supergraph");
|
||||
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
|
||||
pp_string (pp, "\" {\n");
|
||||
gv.indent ();
|
||||
|
||||
gv.println ("overlap=false;");
|
||||
gv.println ("compound=true;");
|
||||
|
||||
/* TODO: maybe (optionally) sub-subdivide by TU, for LTO; see also:
|
||||
https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
|
||||
*/
|
||||
|
||||
/* Break out the supernodes into clusters by function. */
|
||||
{
|
||||
cgraph_node *node;
|
||||
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
|
||||
{
|
||||
function *fun = node->get_fun ();
|
||||
const char *funcname = function_name (fun);
|
||||
gv.println ("subgraph \"cluster_%s\" {",
|
||||
funcname);
|
||||
gv.indent ();
|
||||
pp_printf (pp,
|
||||
("style=\"dashed\";"
|
||||
" color=\"black\";"
|
||||
" label=\"%s\";\n"),
|
||||
funcname);
|
||||
|
||||
/* Break out the nodes into clusters by BB from original CFG. */
|
||||
{
|
||||
basic_block bb;
|
||||
FOR_ALL_BB_FN (bb, fun)
|
||||
{
|
||||
if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
|
||||
{
|
||||
gv.println ("subgraph \"cluster_%s_bb_%i\" {",
|
||||
funcname, bb->index);
|
||||
gv.indent ();
|
||||
pp_printf (pp,
|
||||
("style=\"dashed\";"
|
||||
" color=\"black\";"
|
||||
" label=\"bb: %i\";\n"),
|
||||
bb->index);
|
||||
}
|
||||
|
||||
// TODO: maybe keep an index per-function/per-bb to speed this up???
|
||||
int i;
|
||||
supernode *n;
|
||||
FOR_EACH_VEC_ELT (m_nodes, i, n)
|
||||
if (n->m_fun == fun && n->m_bb == bb)
|
||||
n->dump_dot (&gv, dump_args);
|
||||
|
||||
if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
|
||||
{
|
||||
/* Terminate per-bb "subgraph" */
|
||||
gv.outdent ();
|
||||
gv.println ("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add an invisible edge from ENTRY to EXIT, to improve the graph layout. */
|
||||
pp_string (pp, "\t");
|
||||
get_node_for_function_entry (fun)->dump_dot_id (pp);
|
||||
pp_string (pp, ":s -> ");
|
||||
get_node_for_function_exit (fun)->dump_dot_id (pp);
|
||||
pp_string (pp, ":n [style=\"invis\",constraint=true];\n");
|
||||
|
||||
/* Terminate per-function "subgraph" */
|
||||
gv.outdent ();
|
||||
gv.println ("}");
|
||||
}
|
||||
}
|
||||
|
||||
/* Superedges. */
|
||||
int i;
|
||||
superedge *e;
|
||||
FOR_EACH_VEC_ELT (m_edges, i, e)
|
||||
e->dump_dot (&gv, dump_args);
|
||||
|
||||
/* Terminate "digraph" */
|
||||
gv.outdent ();
|
||||
gv.println ("}");
|
||||
}
|
||||
|
||||
/* Dump this graph in .dot format to FP, using DUMP_ARGS. */
|
||||
|
||||
void
|
||||
supergraph::dump_dot_to_file (FILE *fp, const dump_args_t &dump_args) const
|
||||
{
|
||||
pretty_printer *pp = global_dc->printer->clone ();
|
||||
pp_show_color (pp) = 0;
|
||||
/* %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 (pp) = default_tree_printer;
|
||||
|
||||
pp->buffer->stream = fp;
|
||||
dump_dot_to_pp (pp, dump_args);
|
||||
pp_flush (pp);
|
||||
delete pp;
|
||||
}
|
||||
|
||||
/* Dump this graph in .dot format to PATH, using DUMP_ARGS. */
|
||||
|
||||
void
|
||||
supergraph::dump_dot (const char *path, const dump_args_t &dump_args) const
|
||||
{
|
||||
FILE *fp = fopen (path, "w");
|
||||
dump_dot_to_file (fp, dump_args);
|
||||
fclose (fp);
|
||||
}
|
||||
|
||||
/* Create a supernode for BB within FUN and add it to this supergraph.
|
||||
|
||||
If RETURNING_CALL is non-NULL, the supernode represents the resumption
|
||||
of the basic block after returning from that call.
|
||||
|
||||
If PHI_NODES is non-NULL, this is the initial supernode for the basic
|
||||
block, and is responsible for any handling of the phi nodes. */
|
||||
|
||||
supernode *
|
||||
supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
|
||||
gimple_seq phi_nodes)
|
||||
{
|
||||
supernode *n = new supernode (fun, bb, returning_call, phi_nodes,
|
||||
m_nodes.length ());
|
||||
m_nodes.safe_push (n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Create a new cfg_superedge from SRC to DEST for the underlying CFG edge E,
|
||||
adding it to this supergraph.
|
||||
|
||||
If the edge is for a switch statement, create a switch_cfg_superedge
|
||||
subclass using IDX (the index of E within the out-edges from SRC's
|
||||
underlying basic block). */
|
||||
|
||||
cfg_superedge *
|
||||
supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
|
||||
{
|
||||
/* Special-case switch edges. */
|
||||
gimple *stmt = src->get_last_stmt ();
|
||||
cfg_superedge *new_edge;
|
||||
if (stmt && stmt->code == GIMPLE_SWITCH)
|
||||
new_edge = new switch_cfg_superedge (src, dest, e, idx);
|
||||
else
|
||||
new_edge = new cfg_superedge (src, dest, e);
|
||||
add_edge (new_edge);
|
||||
return new_edge;
|
||||
}
|
||||
|
||||
/* Create and add a call_superedge representing an interprocedural call
|
||||
from SRC to DEST, using CEDGE. */
|
||||
|
||||
call_superedge *
|
||||
supergraph::add_call_superedge (supernode *src, supernode *dest,
|
||||
cgraph_edge *cedge)
|
||||
{
|
||||
call_superedge *new_edge = new call_superedge (src, dest, cedge);
|
||||
add_edge (new_edge);
|
||||
return new_edge;
|
||||
}
|
||||
|
||||
/* Create and add a return_superedge representing returning from an
|
||||
interprocedural call, returning from SRC to DEST, using CEDGE. */
|
||||
|
||||
return_superedge *
|
||||
supergraph::add_return_superedge (supernode *src, supernode *dest,
|
||||
cgraph_edge *cedge)
|
||||
{
|
||||
return_superedge *new_edge = new return_superedge (src, dest, cedge);
|
||||
add_edge (new_edge);
|
||||
return new_edge;
|
||||
}
|
||||
|
||||
/* Implementation of dnode::dump_dot vfunc for supernodes.
|
||||
|
||||
Write a cluster for the node, and within it a .dot node showing
|
||||
the phi nodes and stmts. Call into any node annotator from ARGS to
|
||||
potentially add other records to the cluster. */
|
||||
|
||||
void
|
||||
supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const
|
||||
{
|
||||
gv->println ("subgraph cluster_node_%i {",
|
||||
m_index);
|
||||
gv->indent ();
|
||||
|
||||
gv->println("style=\"solid\";");
|
||||
gv->println("color=\"black\";");
|
||||
gv->println("fillcolor=\"lightgrey\";");
|
||||
gv->println("label=\"sn: %i\";", m_index);
|
||||
|
||||
pretty_printer *pp = gv->get_pp ();
|
||||
|
||||
if (args.m_node_annotator)
|
||||
args.m_node_annotator->add_node_annotations (gv, *this);
|
||||
|
||||
gv->write_indent ();
|
||||
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);
|
||||
|
||||
if (m_returning_call)
|
||||
{
|
||||
gv->begin_tr ();
|
||||
pp_string (pp, "returning call: ");
|
||||
gv->end_tr ();
|
||||
|
||||
gv->begin_tr ();
|
||||
pp_gimple_stmt_1 (pp, m_returning_call, 0, (dump_flags_t)0);
|
||||
pp_write_text_as_html_like_dot_to_stream (pp);
|
||||
gv->end_tr ();
|
||||
|
||||
if (args.m_node_annotator)
|
||||
args.m_node_annotator->add_stmt_annotations (gv, m_returning_call);
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
if (entry_p ())
|
||||
{
|
||||
pp_string (pp, "<TR><TD>ENTRY</TD></TR>");
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
if (return_p ())
|
||||
{
|
||||
pp_string (pp, "<TR><TD>EXIT</TD></TR>");
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
/* Phi nodes. */
|
||||
for (gphi_iterator gpi = const_cast<supernode *> (this)->start_phis ();
|
||||
!gsi_end_p (gpi); gsi_next (&gpi))
|
||||
{
|
||||
const gimple *stmt = gsi_stmt (gpi);
|
||||
gv->begin_tr ();
|
||||
pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
|
||||
pp_write_text_as_html_like_dot_to_stream (pp);
|
||||
gv->end_tr ();
|
||||
|
||||
if (args.m_node_annotator)
|
||||
args.m_node_annotator->add_stmt_annotations (gv, stmt);
|
||||
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
/* Statements. */
|
||||
int i;
|
||||
gimple *stmt;
|
||||
FOR_EACH_VEC_ELT (m_stmts, i, stmt)
|
||||
{
|
||||
gv->begin_tr ();
|
||||
pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
|
||||
pp_write_text_as_html_like_dot_to_stream (pp);
|
||||
gv->end_tr ();
|
||||
|
||||
if (args.m_node_annotator)
|
||||
args.m_node_annotator->add_stmt_annotations (gv, stmt);
|
||||
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
pp_string (pp, "</TABLE>>];\n\n");
|
||||
pp_flush (pp);
|
||||
|
||||
/* Terminate "subgraph" */
|
||||
gv->outdent ();
|
||||
gv->println ("}");
|
||||
}
|
||||
|
||||
/* Write an ID for this node to PP, for use in .dot output. */
|
||||
|
||||
void
|
||||
supernode::dump_dot_id (pretty_printer *pp) const
|
||||
{
|
||||
pp_printf (pp, "node_%i", m_index);
|
||||
}
|
||||
|
||||
/* Get a location_t for the start of this supernode. */
|
||||
|
||||
location_t
|
||||
supernode::get_start_location () const
|
||||
{
|
||||
if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
|
||||
return m_returning_call->location;
|
||||
|
||||
int i;
|
||||
gimple *stmt;
|
||||
FOR_EACH_VEC_ELT (m_stmts, i, stmt)
|
||||
if (stmt->location != UNKNOWN_LOCATION)
|
||||
return stmt->location;
|
||||
|
||||
if (entry_p ())
|
||||
{
|
||||
// TWEAK: show the decl instead; this leads to more readable output:
|
||||
return DECL_SOURCE_LOCATION (m_fun->decl);
|
||||
|
||||
return m_fun->function_start_locus;
|
||||
}
|
||||
if (return_p ())
|
||||
return m_fun->function_end_locus;
|
||||
|
||||
return UNKNOWN_LOCATION;
|
||||
}
|
||||
|
||||
/* Get a location_t for the end of this supernode. */
|
||||
|
||||
location_t
|
||||
supernode::get_end_location () const
|
||||
{
|
||||
int i;
|
||||
gimple *stmt;
|
||||
FOR_EACH_VEC_ELT_REVERSE (m_stmts, i, stmt)
|
||||
if (stmt->location != UNKNOWN_LOCATION)
|
||||
return stmt->location;
|
||||
|
||||
if (m_returning_call && m_returning_call->location != UNKNOWN_LOCATION)
|
||||
return m_returning_call->location;
|
||||
|
||||
if (entry_p ())
|
||||
return m_fun->function_start_locus;
|
||||
if (return_p ())
|
||||
return m_fun->function_end_locus;
|
||||
|
||||
return UNKNOWN_LOCATION;
|
||||
}
|
||||
|
||||
/* Given STMT within this supernode, return its index within m_stmts. */
|
||||
|
||||
unsigned int
|
||||
supernode::get_stmt_index (const gimple *stmt) const
|
||||
{
|
||||
unsigned i;
|
||||
gimple *iter_stmt;
|
||||
FOR_EACH_VEC_ELT (m_stmts, i, iter_stmt)
|
||||
if (iter_stmt == stmt)
|
||||
return i;
|
||||
gcc_unreachable ();
|
||||
}
|
||||
|
||||
/* Implementation of dedge::dump_dot for superedges.
|
||||
Write a .dot edge to GV representing this superedge. */
|
||||
|
||||
void
|
||||
superedge::dump_dot (graphviz_out *gv, const dump_args_t &) const
|
||||
{
|
||||
const char *style = "\"solid,bold\"";
|
||||
const char *color = "black";
|
||||
int weight = 10;
|
||||
const char *constraint = "true";
|
||||
|
||||
switch (m_kind)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case SUPEREDGE_CFG_EDGE:
|
||||
break;
|
||||
case SUPEREDGE_CALL:
|
||||
color = "red";
|
||||
break;
|
||||
case SUPEREDGE_RETURN:
|
||||
color = "green";
|
||||
break;
|
||||
case SUPEREDGE_INTRAPROCEDURAL_CALL:
|
||||
style = "\"dotted\"";
|
||||
break;
|
||||
}
|
||||
|
||||
/* Adapted from graph.c:draw_cfg_node_succ_edges. */
|
||||
if (::edge cfg_edge = get_any_cfg_edge ())
|
||||
{
|
||||
if (cfg_edge->flags & EDGE_FAKE)
|
||||
{
|
||||
style = "dotted";
|
||||
color = "green";
|
||||
weight = 0;
|
||||
}
|
||||
else if (cfg_edge->flags & EDGE_DFS_BACK)
|
||||
{
|
||||
style = "\"dotted,bold\"";
|
||||
color = "blue";
|
||||
weight = 10;
|
||||
}
|
||||
else if (cfg_edge->flags & EDGE_FALLTHRU)
|
||||
{
|
||||
color = "blue";
|
||||
weight = 100;
|
||||
}
|
||||
|
||||
if (cfg_edge->flags & EDGE_ABNORMAL)
|
||||
color = "red";
|
||||
}
|
||||
|
||||
gv->write_indent ();
|
||||
|
||||
pretty_printer *pp = gv->get_pp ();
|
||||
|
||||
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,"
|
||||
" ltail=\"cluster_node_%i\", lhead=\"cluster_node_%i\""
|
||||
" headlabel=\""),
|
||||
style, color, weight, constraint,
|
||||
m_src->m_index, m_dest->m_index);
|
||||
|
||||
dump_label_to_pp (pp, false);
|
||||
|
||||
pp_printf (pp, "\"];\n");
|
||||
}
|
||||
|
||||
/* If this is an intraprocedural superedge, return the associated
|
||||
CFG edge. Otherwise, return NULL. */
|
||||
|
||||
::edge
|
||||
superedge::get_any_cfg_edge () const
|
||||
{
|
||||
if (const cfg_superedge *sub = dyn_cast_cfg_superedge ())
|
||||
return sub->get_cfg_edge ();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If this is an interprocedural superedge, return the associated
|
||||
cgraph_edge *. Otherwise, return NULL. */
|
||||
|
||||
cgraph_edge *
|
||||
superedge::get_any_callgraph_edge () const
|
||||
{
|
||||
if (const callgraph_superedge *sub = dyn_cast_callgraph_superedge ())
|
||||
return sub->m_cedge;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Build a description of this superedge (e.g. "true" for the true
|
||||
edge of a conditional, or "case 42:" for a switch case).
|
||||
|
||||
The caller is responsible for freeing the result.
|
||||
|
||||
If USER_FACING is false, the result also contains any underlying
|
||||
CFG edge flags. e.g. " (flags FALLTHRU | DFS_BACK)". */
|
||||
|
||||
char *
|
||||
superedge::get_description (bool user_facing) const
|
||||
{
|
||||
pretty_printer pp;
|
||||
dump_label_to_pp (&pp, user_facing);
|
||||
return xstrdup (pp_formatted_text (&pp));
|
||||
}
|
||||
|
||||
/* Implementation of superedge::dump_label_to_pp for non-switch CFG
|
||||
superedges.
|
||||
|
||||
For true/false edges, print "true" or "false" to PP.
|
||||
|
||||
If USER_FACING is false, also print flags on the underlying CFG edge to
|
||||
PP. */
|
||||
|
||||
void
|
||||
cfg_superedge::dump_label_to_pp (pretty_printer *pp,
|
||||
bool user_facing) const
|
||||
{
|
||||
if (true_value_p ())
|
||||
pp_printf (pp, "true");
|
||||
else if (false_value_p ())
|
||||
pp_printf (pp, "false");
|
||||
|
||||
if (user_facing)
|
||||
return;
|
||||
|
||||
/* Express edge flags as a string with " | " separator.
|
||||
e.g. " (flags FALLTHRU | DFS_BACK)". */
|
||||
if (get_flags ())
|
||||
{
|
||||
pp_string (pp, " (flags ");
|
||||
bool seen_flag = false;
|
||||
#define DEF_EDGE_FLAG(NAME,IDX) \
|
||||
do { \
|
||||
if (get_flags () & EDGE_##NAME) \
|
||||
{ \
|
||||
if (seen_flag) \
|
||||
pp_string (pp, " | "); \
|
||||
pp_printf (pp, "%s", (#NAME)); \
|
||||
seen_flag = true; \
|
||||
} \
|
||||
} while (0);
|
||||
#include "cfg-flags.def"
|
||||
#undef DEF_EDGE_FLAG
|
||||
pp_string (pp, ")");
|
||||
}
|
||||
|
||||
/* Otherwise, no label. */
|
||||
}
|
||||
|
||||
/* Get the phi argument for PHI for this CFG edge. */
|
||||
|
||||
tree
|
||||
cfg_superedge::get_phi_arg (const gphi *phi) const
|
||||
{
|
||||
size_t index = m_cfg_edge->dest_idx;
|
||||
return gimple_phi_arg_def (phi, index);
|
||||
}
|
||||
|
||||
/* Implementation of superedge::dump_label_to_pp for CFG superedges for
|
||||
"switch" statements.
|
||||
|
||||
Print "case VAL:", "case LOWER ... UPPER:", or "default:" to PP. */
|
||||
|
||||
void
|
||||
switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
|
||||
bool user_facing ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
tree case_label = get_case_label ();
|
||||
gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
|
||||
tree lower_bound = CASE_LOW (case_label);
|
||||
tree upper_bound = CASE_HIGH (case_label);
|
||||
if (lower_bound)
|
||||
{
|
||||
pp_printf (pp, "case ");
|
||||
dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
|
||||
if (upper_bound)
|
||||
{
|
||||
pp_printf (pp, " ... ");
|
||||
dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, false);
|
||||
}
|
||||
pp_printf (pp, ":");
|
||||
}
|
||||
else
|
||||
pp_printf (pp, "default:");
|
||||
}
|
||||
|
||||
/* Get the case label for this "switch" superedge. */
|
||||
|
||||
tree
|
||||
switch_cfg_superedge::get_case_label () const
|
||||
{
|
||||
return gimple_switch_label (get_switch_stmt (), m_idx);
|
||||
}
|
||||
|
||||
/* Implementation of superedge::dump_label_to_pp for interprocedural
|
||||
superedges. */
|
||||
|
||||
void
|
||||
callgraph_superedge::dump_label_to_pp (pretty_printer *pp,
|
||||
bool user_facing ATTRIBUTE_UNUSED) const
|
||||
{
|
||||
switch (m_kind)
|
||||
{
|
||||
default:
|
||||
case SUPEREDGE_CFG_EDGE:
|
||||
gcc_unreachable ();
|
||||
|
||||
case SUPEREDGE_CALL:
|
||||
pp_printf (pp, "call");
|
||||
break;
|
||||
|
||||
case SUPEREDGE_RETURN:
|
||||
pp_printf (pp, "return");
|
||||
break;
|
||||
|
||||
case SUPEREDGE_INTRAPROCEDURAL_CALL:
|
||||
pp_printf (pp, "intraproc link");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the function that was called at this interprocedural call/return
|
||||
edge. */
|
||||
|
||||
function *
|
||||
callgraph_superedge::get_callee_function () const
|
||||
{
|
||||
return m_cedge->callee->get_fun ();
|
||||
}
|
||||
|
||||
/* Get the calling function at this interprocedural call/return edge. */
|
||||
|
||||
function *
|
||||
callgraph_superedge::get_caller_function () const
|
||||
{
|
||||
return m_cedge->caller->get_fun ();
|
||||
}
|
||||
|
||||
/* Get the fndecl that was called at this interprocedural call/return
|
||||
edge. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::get_callee_decl () const
|
||||
{
|
||||
return get_callee_function ()->decl;
|
||||
}
|
||||
|
||||
/* Get the calling fndecl at this interprocedural call/return edge. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::get_caller_decl () const
|
||||
{
|
||||
return get_caller_function ()->decl;
|
||||
}
|
||||
|
||||
/* Given PARM_TO_FIND, a PARM_DECL, identify its index (writing it
|
||||
to *OUT if OUT is non-NULL), and return the corresponding argument
|
||||
at the callsite. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::get_arg_for_parm (tree parm_to_find,
|
||||
callsite_expr *out) const
|
||||
{
|
||||
gcc_assert (TREE_CODE (parm_to_find) == PARM_DECL);
|
||||
|
||||
tree callee = get_callee_decl ();
|
||||
|
||||
int i = 0;
|
||||
for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
|
||||
iter_parm = DECL_CHAIN (iter_parm), ++i)
|
||||
{
|
||||
if (iter_parm == parm_to_find)
|
||||
{
|
||||
if (out)
|
||||
*out = callsite_expr::from_zero_based_param (i);
|
||||
return gimple_call_arg (get_call_stmt (), i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Not found. */
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Look for a use of ARG_TO_FIND as an argument at this callsite.
|
||||
If found, return the default SSA def of the corresponding parm within
|
||||
the callee, and if OUT is non-NULL, write the index to *OUT.
|
||||
Only the first match is handled. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::get_parm_for_arg (tree arg_to_find,
|
||||
callsite_expr *out) const
|
||||
{
|
||||
tree callee = get_callee_decl ();
|
||||
|
||||
int i = 0;
|
||||
for (tree iter_parm = DECL_ARGUMENTS (callee); iter_parm;
|
||||
iter_parm = DECL_CHAIN (iter_parm), ++i)
|
||||
{
|
||||
tree param = gimple_call_arg (get_call_stmt (), i);
|
||||
if (arg_to_find == param)
|
||||
{
|
||||
if (out)
|
||||
*out = callsite_expr::from_zero_based_param (i);
|
||||
return ssa_default_def (get_callee_function (), iter_parm);
|
||||
}
|
||||
}
|
||||
|
||||
/* Not found. */
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Map caller_expr back to an expr within the callee, or return NULL_TREE.
|
||||
If non-NULL is returned, populate OUT. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::map_expr_from_caller_to_callee (tree caller_expr,
|
||||
callsite_expr *out) const
|
||||
{
|
||||
/* Is it an argument (actual param)? If so, convert to
|
||||
parameter (formal param). */
|
||||
tree parm = get_parm_for_arg (caller_expr, out);
|
||||
if (parm)
|
||||
return parm;
|
||||
/* Otherwise try return value. */
|
||||
if (caller_expr == gimple_call_lhs (get_call_stmt ()))
|
||||
{
|
||||
if (out)
|
||||
*out = callsite_expr::from_return_value ();
|
||||
return DECL_RESULT (get_callee_decl ());
|
||||
}
|
||||
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Map callee_expr back to an expr within the caller, or return NULL_TREE.
|
||||
If non-NULL is returned, populate OUT. */
|
||||
|
||||
tree
|
||||
callgraph_superedge::map_expr_from_callee_to_caller (tree callee_expr,
|
||||
callsite_expr *out) const
|
||||
{
|
||||
if (callee_expr == NULL_TREE)
|
||||
return NULL_TREE;
|
||||
|
||||
/* If it's a parameter (formal param), get the argument (actual param). */
|
||||
if (TREE_CODE (callee_expr) == PARM_DECL)
|
||||
return get_arg_for_parm (callee_expr, out);
|
||||
|
||||
/* Similar for the default SSA name of the PARM_DECL. */
|
||||
if (TREE_CODE (callee_expr) == SSA_NAME
|
||||
&& SSA_NAME_IS_DEFAULT_DEF (callee_expr)
|
||||
&& TREE_CODE (SSA_NAME_VAR (callee_expr)) == PARM_DECL)
|
||||
return get_arg_for_parm (SSA_NAME_VAR (callee_expr), out);
|
||||
|
||||
/* Otherwise try return value. */
|
||||
if (callee_expr == DECL_RESULT (get_callee_decl ()))
|
||||
{
|
||||
if (out)
|
||||
*out = callsite_expr::from_return_value ();
|
||||
return gimple_call_lhs (get_call_stmt ());
|
||||
}
|
||||
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
554
gcc/analyzer/supergraph.h
Normal file
554
gcc/analyzer/supergraph.h
Normal file
|
@ -0,0 +1,554 @@
|
|||
/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
|
||||
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_SUPERGRAPH_H
|
||||
#define GCC_ANALYZER_SUPERGRAPH_H
|
||||
|
||||
/* Forward decls, using indentation to show inheritance. */
|
||||
|
||||
class supergraph;
|
||||
class supernode;
|
||||
class superedge;
|
||||
class callgraph_superedge;
|
||||
class call_superedge;
|
||||
class return_superedge;
|
||||
class cfg_superedge;
|
||||
class switch_cfg_superedge;
|
||||
class supercluster;
|
||||
class dot_annotator;
|
||||
|
||||
class logger;
|
||||
|
||||
/* An enum for discriminating between superedge subclasses. */
|
||||
|
||||
enum edge_kind
|
||||
{
|
||||
SUPEREDGE_CFG_EDGE,
|
||||
SUPEREDGE_CALL,
|
||||
SUPEREDGE_RETURN,
|
||||
SUPEREDGE_INTRAPROCEDURAL_CALL
|
||||
};
|
||||
|
||||
/* Flags for controlling the appearance of .dot dumps. */
|
||||
|
||||
enum supergraph_dot_flags
|
||||
{
|
||||
SUPERGRAPH_DOT_SHOW_BBS = (1 << 0)
|
||||
};
|
||||
|
||||
/* A traits struct describing the family of node, edge and digraph
|
||||
classes for supergraphs. */
|
||||
|
||||
struct supergraph_traits
|
||||
{
|
||||
typedef supernode node_t;
|
||||
typedef superedge edge_t;
|
||||
typedef supergraph graph_t;
|
||||
struct dump_args_t
|
||||
{
|
||||
dump_args_t (enum supergraph_dot_flags flags,
|
||||
const dot_annotator *node_annotator)
|
||||
: m_flags (flags),
|
||||
m_node_annotator (node_annotator)
|
||||
{}
|
||||
|
||||
enum supergraph_dot_flags m_flags;
|
||||
const dot_annotator *m_node_annotator;
|
||||
};
|
||||
typedef supercluster cluster_t;
|
||||
};
|
||||
|
||||
/* A "supergraph" is a directed graph formed by joining together all CFGs,
|
||||
linking them via interprocedural call and return edges.
|
||||
|
||||
Basic blocks are split at callsites, so that a call statement occurs
|
||||
twice: once at the end of a supernode, and a second instance at the
|
||||
start of the next supernode (to handle the return). */
|
||||
|
||||
class supergraph : public digraph<supergraph_traits>
|
||||
{
|
||||
public:
|
||||
supergraph (logger *logger);
|
||||
|
||||
supernode *get_node_for_function_entry (function *fun) const
|
||||
{
|
||||
return get_node_for_block (ENTRY_BLOCK_PTR_FOR_FN (fun));
|
||||
}
|
||||
|
||||
supernode *get_node_for_function_exit (function *fun) const
|
||||
{
|
||||
return get_node_for_block (EXIT_BLOCK_PTR_FOR_FN (fun));
|
||||
}
|
||||
|
||||
supernode *get_node_for_block (basic_block bb) const
|
||||
{
|
||||
return *const_cast <bb_to_node_t &> (m_bb_to_initial_node).get (bb);
|
||||
}
|
||||
|
||||
/* Get the supernode containing the second half of the gcall *
|
||||
at an interprocedural call, within the caller. */
|
||||
supernode *get_caller_next_node (cgraph_edge *edge) const
|
||||
{
|
||||
return (*const_cast <cgraph_edge_to_node_t &>
|
||||
(m_cgraph_edge_to_caller_next_node).get (edge));
|
||||
}
|
||||
|
||||
call_superedge *get_edge_for_call (cgraph_edge *edge) const
|
||||
{
|
||||
return (*const_cast <cgraph_edge_to_call_superedge_t &>
|
||||
(m_cgraph_edge_to_call_superedge).get (edge));
|
||||
}
|
||||
|
||||
return_superedge *get_edge_for_return (cgraph_edge *edge) const
|
||||
{
|
||||
return (*const_cast <cgraph_edge_to_return_superedge_t &>
|
||||
(m_cgraph_edge_to_return_superedge).get (edge));
|
||||
}
|
||||
|
||||
superedge *get_intraprocedural_edge_for_call (cgraph_edge *edge) const
|
||||
{
|
||||
return (*const_cast <cgraph_edge_to_intraproc_superedge_t &>
|
||||
(m_cgraph_edge_to_intraproc_superedge).get (edge));
|
||||
}
|
||||
|
||||
cfg_superedge *get_edge_for_cfg_edge (edge e) const
|
||||
{
|
||||
return (*const_cast <cfg_edge_to_cfg_superedge_t &>
|
||||
(m_cfg_edge_to_cfg_superedge).get (e));
|
||||
}
|
||||
|
||||
supernode *get_supernode_for_stmt (const gimple *stmt) const
|
||||
{
|
||||
return (*const_cast <stmt_to_node_t &>(m_stmt_to_node_t).get
|
||||
(const_cast <gimple *> (stmt)));
|
||||
}
|
||||
|
||||
void dump_dot_to_pp (pretty_printer *pp, const dump_args_t &) const;
|
||||
void dump_dot_to_file (FILE *fp, const dump_args_t &) const;
|
||||
void dump_dot (const char *path, const dump_args_t &) const;
|
||||
|
||||
int num_nodes () const { return m_nodes.length (); }
|
||||
int num_edges () const { return m_edges.length (); }
|
||||
|
||||
supernode *get_node_by_index (int idx) const
|
||||
{
|
||||
return m_nodes[idx];
|
||||
}
|
||||
|
||||
unsigned get_num_snodes (function *fun) const
|
||||
{
|
||||
function_to_num_snodes_t &map
|
||||
= const_cast <function_to_num_snodes_t &>(m_function_to_num_snodes);
|
||||
return *map.get (fun);
|
||||
}
|
||||
|
||||
private:
|
||||
supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
|
||||
gimple_seq phi_nodes);
|
||||
cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
|
||||
call_superedge *add_call_superedge (supernode *src, supernode *dest,
|
||||
cgraph_edge *cedge);
|
||||
return_superedge *add_return_superedge (supernode *src, supernode *dest,
|
||||
cgraph_edge *cedge);
|
||||
|
||||
/* Data. */
|
||||
|
||||
typedef ordered_hash_map<basic_block, supernode *> bb_to_node_t;
|
||||
bb_to_node_t m_bb_to_initial_node;
|
||||
bb_to_node_t m_bb_to_final_node;
|
||||
|
||||
typedef ordered_hash_map<cgraph_edge *, supernode *> cgraph_edge_to_node_t;
|
||||
cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
|
||||
cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
|
||||
|
||||
typedef ordered_hash_map< ::edge, cfg_superedge *>
|
||||
cfg_edge_to_cfg_superedge_t;
|
||||
cfg_edge_to_cfg_superedge_t m_cfg_edge_to_cfg_superedge;
|
||||
|
||||
typedef ordered_hash_map<cgraph_edge *, call_superedge *>
|
||||
cgraph_edge_to_call_superedge_t;
|
||||
cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
|
||||
|
||||
typedef ordered_hash_map<cgraph_edge *, return_superedge *>
|
||||
cgraph_edge_to_return_superedge_t;
|
||||
cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
|
||||
|
||||
typedef ordered_hash_map<cgraph_edge *, superedge *>
|
||||
cgraph_edge_to_intraproc_superedge_t;
|
||||
cgraph_edge_to_intraproc_superedge_t m_cgraph_edge_to_intraproc_superedge;
|
||||
|
||||
typedef ordered_hash_map<gimple *, supernode *> stmt_to_node_t;
|
||||
stmt_to_node_t m_stmt_to_node_t;
|
||||
|
||||
typedef hash_map<function *, unsigned> function_to_num_snodes_t;
|
||||
function_to_num_snodes_t m_function_to_num_snodes;
|
||||
};
|
||||
|
||||
/* A node within a supergraph. */
|
||||
|
||||
class supernode : public dnode<supergraph_traits>
|
||||
{
|
||||
public:
|
||||
supernode (function *fun, basic_block bb, gcall *returning_call,
|
||||
gimple_seq phi_nodes, int index)
|
||||
: m_fun (fun), m_bb (bb), m_returning_call (returning_call),
|
||||
m_phi_nodes (phi_nodes), m_index (index)
|
||||
{}
|
||||
|
||||
bool entry_p () const
|
||||
{
|
||||
return m_bb == ENTRY_BLOCK_PTR_FOR_FN (m_fun);
|
||||
}
|
||||
|
||||
bool return_p () const
|
||||
{
|
||||
return m_bb == EXIT_BLOCK_PTR_FOR_FN (m_fun);
|
||||
}
|
||||
|
||||
void dump_dot (graphviz_out *gv, const dump_args_t &args) const OVERRIDE;
|
||||
void dump_dot_id (pretty_printer *pp) const;
|
||||
|
||||
location_t get_start_location () const;
|
||||
location_t get_end_location () const;
|
||||
|
||||
/* Returns iterator at the start of the list of phi nodes, if any. */
|
||||
gphi_iterator start_phis ()
|
||||
{
|
||||
gimple_seq *pseq = &m_phi_nodes;
|
||||
|
||||
/* Adapted from gsi_start_1. */
|
||||
gphi_iterator i;
|
||||
|
||||
i.ptr = gimple_seq_first (*pseq);
|
||||
i.seq = pseq;
|
||||
i.bb = i.ptr ? gimple_bb (i.ptr) : NULL;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
gimple *get_last_stmt () const
|
||||
{
|
||||
if (m_stmts.length () == 0)
|
||||
return NULL;
|
||||
return m_stmts[m_stmts.length () - 1];
|
||||
}
|
||||
|
||||
gcall *get_final_call () const
|
||||
{
|
||||
gimple *stmt = get_last_stmt ();
|
||||
if (stmt == NULL)
|
||||
return NULL;
|
||||
return dyn_cast<gcall *> (stmt);
|
||||
}
|
||||
|
||||
unsigned int get_stmt_index (const gimple *stmt) const;
|
||||
|
||||
function * const m_fun; // alternatively could be stored as runs of indices within the supergraph
|
||||
const basic_block m_bb;
|
||||
gcall * const m_returning_call; // for handling the result of a returned call
|
||||
gimple_seq m_phi_nodes; // ptr to that of the underlying BB, for the first supernode for the BB
|
||||
auto_vec<gimple *> m_stmts;
|
||||
const int m_index; /* unique within the supergraph as a whole. */
|
||||
};
|
||||
|
||||
/* An abstract base class encapsulating an edge within a supergraph.
|
||||
Edges can be CFG edges, or calls/returns for callgraph edges. */
|
||||
|
||||
class superedge : public dedge<supergraph_traits>
|
||||
{
|
||||
public:
|
||||
virtual ~superedge () {}
|
||||
|
||||
void dump_dot (graphviz_out *gv, const dump_args_t &args) const;
|
||||
|
||||
virtual void dump_label_to_pp (pretty_printer *pp,
|
||||
bool user_facing) const = 0;
|
||||
|
||||
enum edge_kind get_kind () const { return m_kind; }
|
||||
|
||||
virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
|
||||
virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
|
||||
virtual const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const { return NULL; }
|
||||
virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
|
||||
virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return NULL; }
|
||||
virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
|
||||
virtual const call_superedge *dyn_cast_call_superedge () const { return NULL; }
|
||||
virtual return_superedge *dyn_cast_return_superedge () { return NULL; }
|
||||
virtual const return_superedge *dyn_cast_return_superedge () const { return NULL; }
|
||||
|
||||
::edge get_any_cfg_edge () const;
|
||||
cgraph_edge *get_any_callgraph_edge () const;
|
||||
|
||||
char *get_description (bool user_facing) const;
|
||||
|
||||
protected:
|
||||
superedge (supernode *src, supernode *dest, enum edge_kind kind)
|
||||
: dedge (src, dest),
|
||||
m_kind (kind)
|
||||
{}
|
||||
|
||||
public:
|
||||
const enum edge_kind m_kind;
|
||||
};
|
||||
|
||||
/* An ID representing an expression at a callsite:
|
||||
either a parameter index, or the return value (or unknown). */
|
||||
|
||||
class callsite_expr
|
||||
{
|
||||
public:
|
||||
callsite_expr () : m_val (-1) {}
|
||||
|
||||
static callsite_expr from_zero_based_param (int idx)
|
||||
{
|
||||
return callsite_expr (idx + 1);
|
||||
}
|
||||
|
||||
static callsite_expr from_return_value ()
|
||||
{
|
||||
return callsite_expr (0);
|
||||
}
|
||||
|
||||
bool param_p () const
|
||||
{
|
||||
return m_val > 0;
|
||||
}
|
||||
|
||||
bool return_value_p () const
|
||||
{
|
||||
return m_val == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
callsite_expr (int val) : m_val (val) {}
|
||||
|
||||
int m_val; /* 1-based parm, 0 for return value, or -1 for "unknown". */
|
||||
};
|
||||
|
||||
/* A subclass of superedge with an associated callgraph edge (either a
|
||||
call or a return). */
|
||||
|
||||
class callgraph_superedge : public superedge
|
||||
{
|
||||
public:
|
||||
callgraph_superedge (supernode *src, supernode *dst, enum edge_kind kind,
|
||||
cgraph_edge *cedge)
|
||||
: superedge (src, dst, kind),
|
||||
m_cedge (cedge)
|
||||
{}
|
||||
|
||||
void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
|
||||
FINAL OVERRIDE;
|
||||
|
||||
function *get_callee_function () const;
|
||||
function *get_caller_function () const;
|
||||
tree get_callee_decl () const;
|
||||
tree get_caller_decl () const;
|
||||
gcall *get_call_stmt () const { return m_cedge->call_stmt; }
|
||||
tree get_arg_for_parm (tree parm, callsite_expr *out) const;
|
||||
tree get_parm_for_arg (tree arg, callsite_expr *out) const;
|
||||
tree map_expr_from_caller_to_callee (tree caller_expr,
|
||||
callsite_expr *out) const;
|
||||
tree map_expr_from_callee_to_caller (tree callee_expr,
|
||||
callsite_expr *out) const;
|
||||
|
||||
cgraph_edge *const m_cedge;
|
||||
};
|
||||
|
||||
template <>
|
||||
template <>
|
||||
inline bool
|
||||
is_a_helper <const callgraph_superedge *>::test (const superedge *sedge)
|
||||
{
|
||||
return (sedge->get_kind () == SUPEREDGE_INTRAPROCEDURAL_CALL
|
||||
|| sedge->get_kind () == SUPEREDGE_CALL
|
||||
|| sedge->get_kind () == SUPEREDGE_RETURN);
|
||||
}
|
||||
|
||||
/* A subclass of superedge representing an interprocedural call. */
|
||||
|
||||
class call_superedge : public callgraph_superedge
|
||||
{
|
||||
public:
|
||||
call_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
|
||||
: callgraph_superedge (src, dst, SUPEREDGE_CALL, cedge)
|
||||
{}
|
||||
|
||||
callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
const callgraph_superedge *dyn_cast_callgraph_superedge () const
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
call_superedge *dyn_cast_call_superedge () FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
const call_superedge *dyn_cast_call_superedge () const FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
return_superedge *get_edge_for_return (const supergraph &sg) const
|
||||
{
|
||||
return sg.get_edge_for_return (m_cedge);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
template <>
|
||||
inline bool
|
||||
is_a_helper <const call_superedge *>::test (const superedge *sedge)
|
||||
{
|
||||
return sedge->get_kind () == SUPEREDGE_CALL;
|
||||
}
|
||||
|
||||
/* A subclass of superedge represesnting an interprocedural return. */
|
||||
|
||||
class return_superedge : public callgraph_superedge
|
||||
{
|
||||
public:
|
||||
return_superedge (supernode *src, supernode *dst, cgraph_edge *cedge)
|
||||
: callgraph_superedge (src, dst, SUPEREDGE_RETURN, cedge)
|
||||
{}
|
||||
|
||||
callgraph_superedge *dyn_cast_callgraph_superedge () FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
const callgraph_superedge *dyn_cast_callgraph_superedge () const
|
||||
FINAL OVERRIDE
|
||||
{ return this; }
|
||||
|
||||
return_superedge *dyn_cast_return_superedge () FINAL OVERRIDE { return this; }
|
||||
const return_superedge *dyn_cast_return_superedge () const FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
call_superedge *get_edge_for_call (const supergraph &sg) const
|
||||
{
|
||||
return sg.get_edge_for_call (m_cedge);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
template <>
|
||||
inline bool
|
||||
is_a_helper <const return_superedge *>::test (const superedge *sedge)
|
||||
{
|
||||
return sedge->get_kind () == SUPEREDGE_RETURN;
|
||||
}
|
||||
|
||||
/* A subclass of superedge that corresponds to a CFG edge. */
|
||||
|
||||
class cfg_superedge : public superedge
|
||||
{
|
||||
public:
|
||||
cfg_superedge (supernode *src, supernode *dst, ::edge e)
|
||||
: superedge (src, dst, SUPEREDGE_CFG_EDGE),
|
||||
m_cfg_edge (e)
|
||||
{}
|
||||
|
||||
void dump_label_to_pp (pretty_printer *pp, bool user_facing) const OVERRIDE;
|
||||
cfg_superedge *dyn_cast_cfg_superedge () FINAL OVERRIDE { return this; }
|
||||
const cfg_superedge *dyn_cast_cfg_superedge () const FINAL OVERRIDE { return this; }
|
||||
|
||||
::edge get_cfg_edge () const { return m_cfg_edge; }
|
||||
int get_flags () const { return m_cfg_edge->flags; }
|
||||
int true_value_p () const { return get_flags () & EDGE_TRUE_VALUE; }
|
||||
int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; }
|
||||
int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
|
||||
|
||||
tree get_phi_arg (const gphi *phi) const;
|
||||
|
||||
private:
|
||||
const ::edge m_cfg_edge;
|
||||
};
|
||||
|
||||
template <>
|
||||
template <>
|
||||
inline bool
|
||||
is_a_helper <const cfg_superedge *>::test (const superedge *sedge)
|
||||
{
|
||||
return sedge->get_kind () == SUPEREDGE_CFG_EDGE;
|
||||
}
|
||||
|
||||
/* A subclass for edges from switch statements, retaining enough
|
||||
information to identify the pertinent case, and for adding labels
|
||||
when rendering via graphviz. */
|
||||
|
||||
class switch_cfg_superedge : public cfg_superedge {
|
||||
public:
|
||||
switch_cfg_superedge (supernode *src, supernode *dst, ::edge e, int idx)
|
||||
: cfg_superedge (src, dst, e),
|
||||
m_idx (idx)
|
||||
{}
|
||||
|
||||
const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
void dump_label_to_pp (pretty_printer *pp, bool user_facing) const
|
||||
FINAL OVERRIDE;
|
||||
|
||||
gswitch *get_switch_stmt () const
|
||||
{
|
||||
return as_a <gswitch *> (m_src->get_last_stmt ());
|
||||
}
|
||||
|
||||
tree get_case_label () const;
|
||||
|
||||
private:
|
||||
const int m_idx;
|
||||
};
|
||||
|
||||
template <>
|
||||
template <>
|
||||
inline bool
|
||||
is_a_helper <const switch_cfg_superedge *>::test (const superedge *sedge)
|
||||
{
|
||||
return sedge->dyn_cast_switch_cfg_superedge () != NULL;
|
||||
}
|
||||
|
||||
/* Base class for adding additional content to the .dot output
|
||||
for a supergraph. */
|
||||
|
||||
class dot_annotator
|
||||
{
|
||||
public:
|
||||
virtual ~dot_annotator () {}
|
||||
virtual void add_node_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
|
||||
const supernode &n ATTRIBUTE_UNUSED)
|
||||
const {}
|
||||
virtual void add_stmt_annotations (graphviz_out *gv ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt ATTRIBUTE_UNUSED)
|
||||
const {}
|
||||
};
|
||||
|
||||
extern cgraph_edge *supergraph_call_edge (function *fun, gimple *stmt);
|
||||
|
||||
#endif /* GCC_ANALYZER_SUPERGRAPH_H */
|
|
@ -989,6 +989,10 @@ fallow-store-data-races
|
|||
Common Report Var(flag_store_data_races) Optimization
|
||||
Allow the compiler to introduce new data races on stores.
|
||||
|
||||
fanalyzer
|
||||
Common Var(flag_analyzer)
|
||||
Enable static analysis pass.
|
||||
|
||||
fargument-alias
|
||||
Common Ignore
|
||||
Does nothing. Preserved for backward compatibility.
|
||||
|
|
|
@ -76,6 +76,12 @@
|
|||
#endif
|
||||
|
||||
|
||||
/* Define 0/1 if static analyzer feature is enabled. */
|
||||
#ifndef USED_FOR_TARGET
|
||||
#undef ENABLE_ANALYZER
|
||||
#endif
|
||||
|
||||
|
||||
/* Define if you want assertions enabled. This is a cheap check. */
|
||||
#ifndef USED_FOR_TARGET
|
||||
#undef ENABLE_ASSERT_CHECKING
|
||||
|
|
25
gcc/configure
vendored
25
gcc/configure
vendored
|
@ -958,6 +958,7 @@ enable_fixed_point
|
|||
enable_threads
|
||||
enable_tls
|
||||
enable_vtable_verify
|
||||
enable_analyzer
|
||||
enable_objc_gc
|
||||
with_dwarf2
|
||||
enable_shared
|
||||
|
@ -1685,6 +1686,7 @@ Optional Features:
|
|||
--enable-tls enable or disable generation of tls code overriding
|
||||
the assembler check for tls support
|
||||
--enable-vtable-verify enable vtable verification feature
|
||||
--disable-analyzer disable -fanalyzer static analyzer
|
||||
--enable-objc-gc enable the use of Boehm's garbage collector with the
|
||||
GNU Objective-C runtime
|
||||
--disable-shared don't provide a shared libgcc
|
||||
|
@ -7703,6 +7705,23 @@ cat >>confdefs.h <<_ACEOF
|
|||
_ACEOF
|
||||
|
||||
|
||||
# Check whether --enable-analyzer was given.
|
||||
if test "${enable_analyzer+set}" = set; then :
|
||||
enableval=$enable_analyzer; if test x$enable_analyzer = xno; then
|
||||
analyzer=0
|
||||
else
|
||||
analyzer=1
|
||||
fi
|
||||
else
|
||||
analyzer=1
|
||||
fi
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define ENABLE_ANALYZER $analyzer
|
||||
_ACEOF
|
||||
|
||||
|
||||
# Check whether --enable-objc-gc was given.
|
||||
if test "${enable_objc_gc+set}" = set; then :
|
||||
enableval=$enable_objc_gc; if test x$enable_objc_gc = xno; then
|
||||
|
@ -18938,7 +18957,7 @@ else
|
|||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 18941 "configure"
|
||||
#line 18960 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
|
@ -19044,7 +19063,7 @@ else
|
|||
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
|
||||
lt_status=$lt_dlunknown
|
||||
cat > conftest.$ac_ext <<_LT_EOF
|
||||
#line 19047 "configure"
|
||||
#line 19066 "configure"
|
||||
#include "confdefs.h"
|
||||
|
||||
#if HAVE_DLFCN_H
|
||||
|
@ -31960,7 +31979,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
|
|||
"depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;;
|
||||
"gccdepdir":C)
|
||||
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
|
||||
for lang in $subdirs c-family common
|
||||
for lang in $subdirs c-family common analyzer
|
||||
do
|
||||
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
|
||||
done ;;
|
||||
|
|
|
@ -909,6 +909,18 @@ vtable_verify=`if test x$enable_vtable_verify = xyes; then echo 1; else echo 0;
|
|||
AC_DEFINE_UNQUOTED(ENABLE_VTABLE_VERIFY, $vtable_verify,
|
||||
[Define 0/1 if vtable verification feature is enabled.])
|
||||
|
||||
AC_ARG_ENABLE(analyzer,
|
||||
[AS_HELP_STRING([--disable-analyzer],
|
||||
[disable -fanalyzer static analyzer])],
|
||||
if test x$enable_analyzer = xno; then
|
||||
analyzer=0
|
||||
else
|
||||
analyzer=1
|
||||
fi,
|
||||
analyzer=1)
|
||||
AC_DEFINE_UNQUOTED(ENABLE_ANALYZER, $analyzer,
|
||||
[Define 0/1 if static analyzer feature is enabled.])
|
||||
|
||||
AC_ARG_ENABLE(objc-gc,
|
||||
[AS_HELP_STRING([--enable-objc-gc],
|
||||
[enable the use of Boehm's garbage collector with
|
||||
|
@ -1214,7 +1226,7 @@ AC_CHECK_HEADERS(ext/hash_map)
|
|||
ZW_CREATE_DEPDIR
|
||||
AC_CONFIG_COMMANDS([gccdepdir],[
|
||||
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
|
||||
for lang in $subdirs c-family common
|
||||
for lang in $subdirs c-family common analyzer
|
||||
do
|
||||
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
|
||||
done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR])
|
||||
|
|
188
gcc/digraph.cc
Normal file
188
gcc/digraph.cc
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* Template classes for directed graphs.
|
||||
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 "diagnostic.h"
|
||||
#include "graphviz.h"
|
||||
#include "digraph.h"
|
||||
#include "shortest-paths.h"
|
||||
#include "selftest.h"
|
||||
|
||||
#if CHECKING_P
|
||||
|
||||
namespace selftest {
|
||||
|
||||
/* A family of digraph classes for writing selftests. */
|
||||
|
||||
struct test_node;
|
||||
struct test_edge;
|
||||
struct test_graph;
|
||||
struct test_dump_args_t {};
|
||||
struct test_cluster;
|
||||
|
||||
struct test_graph_traits
|
||||
{
|
||||
typedef test_node node_t;
|
||||
typedef test_edge edge_t;
|
||||
typedef test_graph graph_t;
|
||||
typedef test_dump_args_t dump_args_t;
|
||||
typedef test_cluster cluster_t;
|
||||
};
|
||||
|
||||
struct test_node : public dnode<test_graph_traits>
|
||||
{
|
||||
test_node (const char *name, int index) : m_name (name), m_index (index) {}
|
||||
void dump_dot (graphviz_out *, const dump_args_t &) const OVERRIDE
|
||||
{
|
||||
}
|
||||
|
||||
const char *m_name;
|
||||
int m_index;
|
||||
};
|
||||
|
||||
struct test_edge : public dedge<test_graph_traits>
|
||||
{
|
||||
test_edge (node_t *src, node_t *dest)
|
||||
: dedge (src, dest)
|
||||
{}
|
||||
|
||||
void dump_dot (graphviz_out *gv, const dump_args_t &) const OVERRIDE
|
||||
{
|
||||
gv->println ("%s -> %s;", m_src->m_name, m_dest->m_name);
|
||||
}
|
||||
};
|
||||
|
||||
struct test_graph : public digraph<test_graph_traits>
|
||||
{
|
||||
test_node *add_test_node (const char *name)
|
||||
{
|
||||
test_node *result = new test_node (name, m_nodes.length ());
|
||||
add_node (result);
|
||||
return result;
|
||||
}
|
||||
|
||||
test_edge *add_test_edge (test_node *src, test_node *dst)
|
||||
{
|
||||
test_edge *result = new test_edge (src, dst);
|
||||
add_edge (result);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct test_cluster : public cluster<test_graph_traits>
|
||||
{
|
||||
};
|
||||
|
||||
struct test_path
|
||||
{
|
||||
auto_vec<const test_edge *> m_edges;
|
||||
};
|
||||
|
||||
/* Smoketest of digraph dumping. */
|
||||
|
||||
static void
|
||||
test_dump_to_dot ()
|
||||
{
|
||||
test_graph g;
|
||||
test_node *a = g.add_test_node ("a");
|
||||
test_node *b = g.add_test_node ("b");
|
||||
g.add_test_edge (a, b);
|
||||
|
||||
pretty_printer pp;
|
||||
pp.buffer->stream = NULL;
|
||||
test_dump_args_t dump_args;
|
||||
g.dump_dot_to_pp (&pp, NULL, dump_args);
|
||||
|
||||
ASSERT_STR_CONTAINS (pp_formatted_text (&pp),
|
||||
"a -> b;\n");
|
||||
}
|
||||
|
||||
/* Test shortest paths from A in this digraph,
|
||||
where edges run top-to-bottom if not otherwise labeled:
|
||||
|
||||
A
|
||||
/ \
|
||||
B C-->D
|
||||
| |
|
||||
E |
|
||||
\ /
|
||||
F. */
|
||||
|
||||
static void
|
||||
test_shortest_paths ()
|
||||
{
|
||||
test_graph g;
|
||||
test_node *a = g.add_test_node ("a");
|
||||
test_node *b = g.add_test_node ("b");
|
||||
test_node *c = g.add_test_node ("d");
|
||||
test_node *d = g.add_test_node ("d");
|
||||
test_node *e = g.add_test_node ("e");
|
||||
test_node *f = g.add_test_node ("f");
|
||||
|
||||
test_edge *ab = g.add_test_edge (a, b);
|
||||
test_edge *ac = g.add_test_edge (a, c);
|
||||
test_edge *cd = g.add_test_edge (c, d);
|
||||
test_edge *be = g.add_test_edge (b, e);
|
||||
g.add_test_edge (e, f);
|
||||
test_edge *cf = g.add_test_edge (c, f);
|
||||
|
||||
shortest_paths<test_graph_traits, test_path> sp (g, a);
|
||||
|
||||
test_path path_to_a = sp.get_shortest_path (a);
|
||||
ASSERT_EQ (path_to_a.m_edges.length (), 0);
|
||||
|
||||
test_path path_to_b = sp.get_shortest_path (b);
|
||||
ASSERT_EQ (path_to_b.m_edges.length (), 1);
|
||||
ASSERT_EQ (path_to_b.m_edges[0], ab);
|
||||
|
||||
test_path path_to_c = sp.get_shortest_path (c);
|
||||
ASSERT_EQ (path_to_c.m_edges.length (), 1);
|
||||
ASSERT_EQ (path_to_c.m_edges[0], ac);
|
||||
|
||||
test_path path_to_d = sp.get_shortest_path (d);
|
||||
ASSERT_EQ (path_to_d.m_edges.length (), 2);
|
||||
ASSERT_EQ (path_to_d.m_edges[0], ac);
|
||||
ASSERT_EQ (path_to_d.m_edges[1], cd);
|
||||
|
||||
test_path path_to_e = sp.get_shortest_path (e);
|
||||
ASSERT_EQ (path_to_e.m_edges.length (), 2);
|
||||
ASSERT_EQ (path_to_e.m_edges[0], ab);
|
||||
ASSERT_EQ (path_to_e.m_edges[1], be);
|
||||
|
||||
test_path path_to_f = sp.get_shortest_path (f);
|
||||
ASSERT_EQ (path_to_f.m_edges.length (), 2);
|
||||
ASSERT_EQ (path_to_f.m_edges[0], ac);
|
||||
ASSERT_EQ (path_to_f.m_edges[1], cf);
|
||||
}
|
||||
|
||||
/* Run all of the selftests within this file. */
|
||||
|
||||
void
|
||||
digraph_cc_tests ()
|
||||
{
|
||||
test_dump_to_dot ();
|
||||
test_shortest_paths ();
|
||||
}
|
||||
|
||||
} // namespace selftest
|
||||
|
||||
#endif /* #if CHECKING_P */
|
246
gcc/digraph.h
Normal file
246
gcc/digraph.h
Normal file
|
@ -0,0 +1,246 @@
|
|||
/* Template classes for directed graphs.
|
||||
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_DIGRAPH_H
|
||||
#define GCC_DIGRAPH_H
|
||||
|
||||
#include "diagnostic.h"
|
||||
#include "tree-diagnostic.h" /* for default_tree_printer. */
|
||||
#include "graphviz.h"
|
||||
|
||||
/* Templates for a family of classes: digraph, node, edge, and cluster.
|
||||
This assumes a traits type with the following typedefs:
|
||||
node_t: the node class
|
||||
edge_t: the edge class
|
||||
dump_args_t: additional args for dot-dumps
|
||||
cluster_t: the cluster class (for use when generating .dot files).
|
||||
|
||||
Using a template allows for typesafe nodes and edges: a node's
|
||||
predecessor and successor edges can be of a node-specific edge
|
||||
subclass, without needing casting. */
|
||||
|
||||
/* Abstract base class for a node in a directed graph. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
class dnode
|
||||
{
|
||||
public:
|
||||
typedef typename GraphTraits::edge_t edge_t;
|
||||
typedef typename GraphTraits::dump_args_t dump_args_t;
|
||||
|
||||
virtual ~dnode () {}
|
||||
virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
|
||||
|
||||
auto_vec<edge_t *> m_preds;
|
||||
auto_vec<edge_t *> m_succs;
|
||||
};
|
||||
|
||||
/* Abstract base class for an edge in a directed graph. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
class dedge
|
||||
{
|
||||
public:
|
||||
typedef typename GraphTraits::node_t node_t;
|
||||
typedef typename GraphTraits::dump_args_t dump_args_t;
|
||||
|
||||
dedge (node_t *src, node_t *dest)
|
||||
: m_src (src), m_dest (dest) {}
|
||||
|
||||
virtual ~dedge () {}
|
||||
|
||||
virtual void dump_dot (graphviz_out *gv, const dump_args_t &args) const = 0;
|
||||
|
||||
node_t *const m_src;
|
||||
node_t *const m_dest;
|
||||
};
|
||||
|
||||
/* Abstract base class for a directed graph.
|
||||
This class maintains the vectors of nodes and edges,
|
||||
and owns the nodes and edges. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
class digraph
|
||||
{
|
||||
public:
|
||||
typedef typename GraphTraits::node_t node_t;
|
||||
typedef typename GraphTraits::edge_t edge_t;
|
||||
typedef typename GraphTraits::dump_args_t dump_args_t;
|
||||
typedef typename GraphTraits::cluster_t cluster_t;
|
||||
|
||||
digraph () {}
|
||||
virtual ~digraph () {}
|
||||
|
||||
void dump_dot_to_pp (pretty_printer *pp,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const;
|
||||
void dump_dot_to_file (FILE *fp,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const;
|
||||
void dump_dot (const char *path,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const;
|
||||
|
||||
void add_node (node_t *node);
|
||||
void add_edge (edge_t *edge);
|
||||
|
||||
auto_delete_vec<node_t> m_nodes;
|
||||
auto_delete_vec<edge_t> m_edges;
|
||||
};
|
||||
|
||||
/* Abstract base class for splitting dnodes into hierarchical clusters
|
||||
in the generated .dot file.
|
||||
|
||||
See "Subgraphs and Clusters" within
|
||||
https://www.graphviz.org/doc/info/lang.html
|
||||
and e.g.
|
||||
https://graphviz.gitlab.io/_pages/Gallery/directed/cluster.html
|
||||
|
||||
If a root_cluster is passed to dump_dot*, then all nodes will be
|
||||
added to it at the start of dumping, via calls to add_node.
|
||||
|
||||
The root cluster can organize the nodes into a hierarchy of
|
||||
child clusters.
|
||||
|
||||
After all nodes are added to the root cluster, dump_dot will then
|
||||
be called on it (and not on the nodes themselves). */
|
||||
|
||||
template <typename GraphTraits>
|
||||
class cluster
|
||||
{
|
||||
public:
|
||||
typedef typename GraphTraits::node_t node_t;
|
||||
typedef typename GraphTraits::dump_args_t dump_args_t;
|
||||
|
||||
virtual ~cluster () {}
|
||||
|
||||
virtual void add_node (node_t *node) = 0;
|
||||
|
||||
/* Recursively dump the cluster, all nodes, and child clusters. */
|
||||
virtual void dump_dot (graphviz_out *gv, const dump_args_t &) const = 0;
|
||||
};
|
||||
|
||||
/* Write .dot information for this graph to PP, passing ARGS to the nodes
|
||||
and edges.
|
||||
If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
inline void
|
||||
digraph<GraphTraits>::dump_dot_to_pp (pretty_printer *pp,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const
|
||||
{
|
||||
graphviz_out gv (pp);
|
||||
|
||||
pp_string (pp, "digraph \"");
|
||||
pp_string (pp, "base");
|
||||
pp_string (pp, "\" {\n");
|
||||
|
||||
gv.indent ();
|
||||
|
||||
pp_string (pp, "overlap=false;\n");
|
||||
pp_string (pp, "compound=true;\n");
|
||||
|
||||
/* If using clustering, emit all nodes via clusters. */
|
||||
if (root_cluster)
|
||||
{
|
||||
int i;
|
||||
node_t *n;
|
||||
FOR_EACH_VEC_ELT (m_nodes, i, n)
|
||||
root_cluster->add_node (n);
|
||||
root_cluster->dump_dot (&gv, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise, display all nodes at top level. */
|
||||
int i;
|
||||
node_t *n;
|
||||
FOR_EACH_VEC_ELT (m_nodes, i, n)
|
||||
n->dump_dot (&gv, args);
|
||||
}
|
||||
|
||||
/* Edges. */
|
||||
int i;
|
||||
edge_t *e;
|
||||
FOR_EACH_VEC_ELT (m_edges, i, e)
|
||||
e->dump_dot (&gv, args);
|
||||
|
||||
/* Terminate "digraph" */
|
||||
gv.outdent ();
|
||||
pp_string (pp, "}");
|
||||
pp_newline (pp);
|
||||
}
|
||||
|
||||
/* Write .dot information for this graph to FP, passing ARGS to the nodes
|
||||
and edges.
|
||||
If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
inline void
|
||||
digraph<GraphTraits>::dump_dot_to_file (FILE *fp,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const
|
||||
{
|
||||
pretty_printer pp;
|
||||
// TODO:
|
||||
pp_format_decoder (&pp) = default_tree_printer;
|
||||
pp.buffer->stream = fp;
|
||||
dump_dot_to_pp (&pp, root_cluster, args);
|
||||
pp_flush (&pp);
|
||||
}
|
||||
|
||||
/* Write .dot information for this graph to a file at PATH, passing ARGS
|
||||
to the nodes and edges.
|
||||
If ROOT_CLUSTER is non-NULL, use it to organize the nodes into clusters. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
inline void
|
||||
digraph<GraphTraits>::dump_dot (const char *path,
|
||||
cluster_t *root_cluster,
|
||||
const dump_args_t &args) const
|
||||
{
|
||||
FILE *fp = fopen (path, "w");
|
||||
dump_dot_to_file (fp, root_cluster, args);
|
||||
fclose (fp);
|
||||
}
|
||||
|
||||
/* Add NODE to this DIGRAPH, taking ownership. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
inline void
|
||||
digraph<GraphTraits>::add_node (node_t *node)
|
||||
{
|
||||
m_nodes.safe_push (node);
|
||||
}
|
||||
|
||||
/* Add EDGE to this digraph, and to the preds/succs of its endpoints.
|
||||
Take ownership of EDGE. */
|
||||
|
||||
template <typename GraphTraits>
|
||||
inline void
|
||||
digraph<GraphTraits>::add_edge (edge_t *edge)
|
||||
{
|
||||
m_edges.safe_push (edge);
|
||||
edge->m_dest->m_preds.safe_push (edge);
|
||||
edge->m_src->m_succs.safe_push (edge);
|
||||
|
||||
}
|
||||
|
||||
#endif /* GCC_DIGRAPH_H */
|
513
gcc/doc/analyzer.texi
Normal file
513
gcc/doc/analyzer.texi
Normal file
|
@ -0,0 +1,513 @@
|
|||
@c Copyright (C) 2019 Free Software Foundation, Inc.
|
||||
@c This is part of the GCC manual.
|
||||
@c For copying conditions, see the file gcc.texi.
|
||||
@c Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||||
|
||||
@node Static Analyzer
|
||||
@chapter Static Analyzer
|
||||
@cindex analyzer
|
||||
@cindex static analysis
|
||||
@cindex static analyzer
|
||||
|
||||
@menu
|
||||
* Analyzer Internals:: Analyzer Internals
|
||||
* Debugging the Analyzer:: Useful debugging tips
|
||||
@end menu
|
||||
|
||||
@node Analyzer Internals
|
||||
@section Analyzer Internals
|
||||
@cindex analyzer, internals
|
||||
@cindex static analyzer, internals
|
||||
|
||||
@subsection Overview
|
||||
|
||||
The analyzer implementation works on the gimple-SSA representation.
|
||||
(I chose this in the hopes of making it easy to work with LTO to
|
||||
do whole-program analysis).
|
||||
|
||||
The implementation is read-only: it doesn't attempt to change anything,
|
||||
just emit warnings.
|
||||
|
||||
First, we build a @code{supergraph} which combines the callgraph and all
|
||||
of the CFGs into a single directed graph, with both interprocedural and
|
||||
intraprocedural edges. The nodes and edges in the supergraph are called
|
||||
``supernodes'' and ``superedges'', and often referred to in code as
|
||||
@code{snodes} and @code{sedges}. Basic blocks in the CFGs are split at
|
||||
interprocedural calls, so there can be more than one supernode per
|
||||
basic block. Most statements will be in just one supernode, but a call
|
||||
statement can appear in two supernodes: at the end of one for the call,
|
||||
and again at the start of another for the return.
|
||||
|
||||
The supergraph can be seen using @option{-fdump-analyzer-supergraph}.
|
||||
|
||||
We then build an @code{analysis_plan} which walks the callgraph to
|
||||
determine which calls might be suitable for being summarized (rather
|
||||
than fully explored) and thus in what order to explore the functions.
|
||||
|
||||
Next is the heart of the analyzer: we use a worklist to explore state
|
||||
within the supergraph, building an "exploded graph".
|
||||
Nodes in the exploded graph correspond to <point,@w{ }state> pairs, as in
|
||||
"Precise Interprocedural Dataflow Analysis via Graph Reachability"
|
||||
(Thomas Reps, Susan Horwitz and Mooly Sagiv).
|
||||
|
||||
We reuse nodes for <point, state> pairs we've already seen, and avoid
|
||||
tracking state too closely, so that (hopefully) we rapidly converge
|
||||
on a final exploded graph, and terminate the analysis. We also bail
|
||||
out if the number of exploded <end-of-basic-block, state> nodes gets
|
||||
larger than a particular multiple of the total number of basic blocks
|
||||
(to ensure termination in the face of pathological state-explosion
|
||||
cases, or bugs). We also stop exploring a point once we hit a limit
|
||||
of states for that point.
|
||||
|
||||
We can identify problems directly when processing a <point,@w{ }state>
|
||||
instance. For example, if we're finding the successors of
|
||||
|
||||
@smallexample
|
||||
<point: before-stmt: "free (ptr);",
|
||||
state: @{"ptr": freed@}>
|
||||
@end smallexample
|
||||
|
||||
then we can detect a double-free of "ptr". We can then emit a path
|
||||
to reach the problem by finding the simplest route through the graph.
|
||||
|
||||
Program points in the analysis are much more fine-grained than in the
|
||||
CFG and supergraph, with points (and thus potentially exploded nodes)
|
||||
for various events, including before individual statements.
|
||||
By default the exploded graph merges multiple consecutive statements
|
||||
in a supernode into one exploded edge to minimize the size of the
|
||||
exploded graph. This can be suppressed via
|
||||
@option{-fanalyzer-fine-grained}.
|
||||
The fine-grained approach seems to make things simpler and more debuggable
|
||||
that other approaches I tried, in that each point is responsible for one
|
||||
thing.
|
||||
|
||||
Program points in the analysis also have a "call string" identifying the
|
||||
stack of callsites below them, so that paths in the exploded graph
|
||||
correspond to interprocedurally valid paths: we always return to the
|
||||
correct call site, propagating state information accordingly.
|
||||
We avoid infinite recursion by stopping the analysis if a callsite
|
||||
appears more than @code{analyzer-max-recursion-depth} in a callstring
|
||||
(defaulting to 2).
|
||||
|
||||
@subsection Graphs
|
||||
|
||||
Nodes and edges in the exploded graph are called ``exploded nodes'' and
|
||||
``exploded edges'' and often referred to in the code as
|
||||
@code{enodes} and @code{eedges} (especially when distinguishing them
|
||||
from the @code{snodes} and @code{sedges} in the supergraph).
|
||||
|
||||
Each graph numbers its nodes, giving unique identifiers - supernodes
|
||||
are referred to throughout dumps in the form @samp{SN': @var{index}} and
|
||||
exploded nodes in the form @samp{EN: @var{index}} (e.g. @samp{SN: 2} and
|
||||
@samp{EN:29}).
|
||||
|
||||
The supergraph can be seen using @option{-fdump-analyzer-supergraph-graph}.
|
||||
|
||||
The exploded graph can be seen using @option{-fdump-analyzer-exploded-graph}
|
||||
and other dump options. Exploded nodes are color-coded in the .dot output
|
||||
based on state-machine states to make it easier to see state changes at
|
||||
a glance.
|
||||
|
||||
@subsection State Tracking
|
||||
|
||||
There's a tension between:
|
||||
@itemize @bullet
|
||||
@item
|
||||
precision of analysis in the straight-line case, vs
|
||||
@item
|
||||
exponential blow-up in the face of control flow.
|
||||
@end itemize
|
||||
|
||||
For example, in general, given this CFG:
|
||||
|
||||
@smallexample
|
||||
A
|
||||
/ \
|
||||
B C
|
||||
\ /
|
||||
D
|
||||
/ \
|
||||
E F
|
||||
\ /
|
||||
G
|
||||
@end smallexample
|
||||
|
||||
we want to avoid differences in state-tracking in B and C from
|
||||
leading to blow-up. If we don't prevent state blowup, we end up
|
||||
with exponential growth of the exploded graph like this:
|
||||
|
||||
@smallexample
|
||||
|
||||
1:A
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
2:B 3:C
|
||||
| |
|
||||
4:D 5:D (2 exploded nodes for D)
|
||||
/ \ / \
|
||||
6:E 7:F 8:E 9:F
|
||||
| | | |
|
||||
10:G 11:G 12:G 13:G (4 exploded nodes for G)
|
||||
|
||||
@end smallexample
|
||||
|
||||
Similar issues arise with loops.
|
||||
|
||||
To prevent this, we follow various approaches:
|
||||
|
||||
@enumerate a
|
||||
@item
|
||||
state pruning: which tries to discard state that won't be relevant
|
||||
later on withing the function.
|
||||
This can be disabled via @option{-fno-analyzer-state-purge}.
|
||||
|
||||
@item
|
||||
state merging. We can try to find the commonality between two
|
||||
program_state instances to make a third, simpler program_state.
|
||||
We have two strategies here:
|
||||
|
||||
@enumerate
|
||||
@item
|
||||
the worklist keeps new nodes for the same program_point together,
|
||||
and tries to merge them before processing, and thus before they have
|
||||
successors. Hence, in the above, the two nodes for D (4 and 5) reach
|
||||
the front of the worklist together, and we create a node for D with
|
||||
the merger of the incoming states.
|
||||
|
||||
@item
|
||||
try merging with the state of existing enodes for the program_point
|
||||
(which may have already been explored). There will be duplication,
|
||||
but only one set of duplication; subsequent duplicates are more likely
|
||||
to hit the cache. In particular, (hopefully) all merger chains are
|
||||
finite, and so we guarantee termination.
|
||||
This is intended to help with loops: we ought to explore the first
|
||||
iteration, and then have a "subsequent iterations" exploration,
|
||||
which uses a state merged from that of the first, to be more abstract.
|
||||
@end enumerate
|
||||
|
||||
We avoid merging pairs of states that have state-machine differences,
|
||||
as these are the kinds of differences that are likely to be most
|
||||
interesting. So, for example, given:
|
||||
|
||||
@smallexample
|
||||
if (condition)
|
||||
ptr = malloc (size);
|
||||
else
|
||||
ptr = local_buf;
|
||||
|
||||
.... do things with 'ptr'
|
||||
|
||||
if (condition)
|
||||
free (ptr);
|
||||
|
||||
...etc
|
||||
@end smallexample
|
||||
|
||||
then we end up with an exploded graph that looks like this:
|
||||
|
||||
@smallexample
|
||||
|
||||
if (condition)
|
||||
/ T \ F
|
||||
--------- ----------
|
||||
/ \
|
||||
ptr = malloc (size) ptr = local_buf
|
||||
| |
|
||||
copy of copy of
|
||||
"do things with 'ptr'" "do things with 'ptr'"
|
||||
with ptr: heap-allocated with ptr: stack-allocated
|
||||
| |
|
||||
if (condition) if (condition)
|
||||
| known to be T | known to be F
|
||||
free (ptr); |
|
||||
\ /
|
||||
-----------------------------
|
||||
| ('ptr' is pruned, so states can be merged)
|
||||
etc
|
||||
|
||||
@end smallexample
|
||||
|
||||
where some duplication has occurred, but only for the places where the
|
||||
the different paths are worth exploringly separately.
|
||||
|
||||
Merging can be disabled via @option{-fno-analyzer-state-merge}.
|
||||
@end enumerate
|
||||
|
||||
@subsection Region Model
|
||||
|
||||
Part of the state stored at a @code{exploded_node} is a @code{region_model}.
|
||||
This is an implementation of the region-based ternary model described in
|
||||
@url{http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf,
|
||||
"A Memory Model for Static Analysis of C Programs"}
|
||||
(Zhongxing Xu, Ted Kremenek, and Jian Zhang).
|
||||
|
||||
A @code{region_model} encapsulates a representation of the state of
|
||||
memory, with a tree of @code{region} instances, along with their associated
|
||||
values. The representation is graph-like because values can be pointers
|
||||
to regions. It also stores a constraint_manager, capturing relationships
|
||||
between the values.
|
||||
|
||||
Because each node in the @code{exploded_graph} has a @code{region_model},
|
||||
and each of the latter is graph-like, the @code{exploded_graph} is in some
|
||||
ways a graph of graphs.
|
||||
|
||||
Here's an example of printing a @code{region_model}, showing the ASCII-art
|
||||
used to visualize the region hierarchy (colorized when printing to stderr):
|
||||
|
||||
@smallexample
|
||||
(gdb) call debug (*this)
|
||||
r0: @{kind: 'root', parent: null, sval: null@}
|
||||
|-stack: r1: @{kind: 'stack', parent: r0, sval: sv1@}
|
||||
| |: sval: sv1: @{poisoned: uninit@}
|
||||
| |-frame for 'test': r2: @{kind: 'frame', parent: r1, sval: null, map: @{'ptr_3': r3@}, function: 'test', depth: 0@}
|
||||
| | `-'ptr_3': r3: @{kind: 'map', parent: r2, sval: sv3, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv3: @{type: 'void *', unknown@}
|
||||
| | |: type: 'void *'
|
||||
| `-frame for 'calls_malloc': r4: @{kind: 'frame', parent: r1, sval: null, map: @{'result_3': r7, '_4': r8, '<anonymous>': r5@}, function: 'calls_malloc', depth: 1@}
|
||||
| |-'<anonymous>': r5: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| | |: type: 'void *'
|
||||
| |-'result_3': r7: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| | |: type: 'void *'
|
||||
| `-'_4': r8: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| |: type: 'void *'
|
||||
`-heap: r9: @{kind: 'heap', parent: r0, sval: sv2@}
|
||||
|: sval: sv2: @{poisoned: uninit@}
|
||||
`-r6: @{kind: 'symbolic', parent: r9, sval: null, map: @{@}@}
|
||||
svalues:
|
||||
sv0: @{type: 'size_t', '1024'@}
|
||||
sv1: @{poisoned: uninit@}
|
||||
sv2: @{poisoned: uninit@}
|
||||
sv3: @{type: 'void *', unknown@}
|
||||
sv4: @{type: 'void *', &r6@}
|
||||
constraint manager:
|
||||
equiv classes:
|
||||
ec0: @{sv0 == '1024'@}
|
||||
ec1: @{sv4@}
|
||||
constraints:
|
||||
@end smallexample
|
||||
|
||||
This is the state at the point of returning from @code{calls_malloc} back
|
||||
to @code{test} in the following:
|
||||
|
||||
@smallexample
|
||||
void *
|
||||
calls_malloc (void)
|
||||
@{
|
||||
void *result = malloc (1024);
|
||||
return result;
|
||||
@}
|
||||
|
||||
void test (void)
|
||||
@{
|
||||
void *ptr = calls_malloc ();
|
||||
/* etc. */
|
||||
@}
|
||||
@end smallexample
|
||||
|
||||
The ``root'' region (``r0'') has a ``stack'' child (``r1''), with two
|
||||
children: a frame for @code{test} (``r2''), and a frame for
|
||||
@code{calls_malloc} (``r4''). These frame regions have child regions for
|
||||
storing their local variables. For example, the return region
|
||||
and that of various other regions within the ``calls_malloc'' frame all have
|
||||
value ``sv4'', a pointer to a heap-allocated region ``r6''. Within the parent
|
||||
frame, @code{ptr_3} has value ``sv3'', an unknown @code{void *}.
|
||||
|
||||
@subsection Analyzer Paths
|
||||
|
||||
We need to explain to the user what the problem is, and to persuade them
|
||||
that there really is a problem. Hence having a @code{diagnostic_path}
|
||||
isn't just an incidental detail of the analyzer; it's required.
|
||||
|
||||
Paths ought to be:
|
||||
@itemize @bullet
|
||||
@item
|
||||
interprocedurally-valid
|
||||
@item
|
||||
feasible
|
||||
@end itemize
|
||||
|
||||
Without state-merging, all paths in the exploded graph are feasible
|
||||
(in terms of constraints being satisified).
|
||||
With state-merging, paths in the exploded graph can be infeasible.
|
||||
|
||||
We collate warnings and only emit them for the simplest path
|
||||
e.g. for a bug in a utility function, with lots of routes to calling it,
|
||||
we only emit the simplest path (which could be intraprocedural, if
|
||||
it can be reproduced without a caller). We apply a check that
|
||||
each duplicate warning's shortest path is feasible, rejecting any
|
||||
warnings for which the shortest path is infeasible (which could lead to
|
||||
false negatives).
|
||||
|
||||
We use the shortest feasible @code{exploded_path} through the
|
||||
@code{exploded_graph} (a list of @code{exploded_edge *}) to build a
|
||||
@code{diagnostic_path} (a list of events for the diagnostic subsystem) -
|
||||
specifically a @code{checker_path}.
|
||||
|
||||
Having built the @code{checker_path}, we prune it to try to eliminate
|
||||
events that aren't relevant, to minimize how much the user has to read.
|
||||
|
||||
After pruning, we notify each event in the path of its ID and record the
|
||||
IDs of interesting events, allowing for events to refer to other events
|
||||
in their descriptions. The @code{pending_diagnostic} class has various
|
||||
vfuncs to support emitting more precise descriptions, so that e.g.
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
a deref-of-unchecked-malloc diagnostic might use:
|
||||
@smallexample
|
||||
returning possibly-NULL pointer to 'make_obj' from 'allocator'
|
||||
@end smallexample
|
||||
for a @code{return_event} to make it clearer how the unchecked value moves
|
||||
from callee back to caller
|
||||
@item
|
||||
a double-free diagnostic might use:
|
||||
@smallexample
|
||||
second 'free' here; first 'free' was at (3)
|
||||
@end smallexample
|
||||
and a use-after-free might use
|
||||
@smallexample
|
||||
use after 'free' here; memory was freed at (2)
|
||||
@end smallexample
|
||||
@end itemize
|
||||
|
||||
At this point we can emit the diagnostic.
|
||||
|
||||
@subsection Limitations
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Only for C so far
|
||||
@item
|
||||
The implementation of call summaries is currently very simplistic.
|
||||
@item
|
||||
Lack of function pointer analysis
|
||||
@item
|
||||
The region model code creates lots of little mutable objects at each
|
||||
@code{region_model} (and thus per @code{exploded_node}) rather than
|
||||
sharing immutable objects and having the mutable state in the
|
||||
@code{program_state} or @code{region_model}. The latter approach might be
|
||||
more efficient, and might avoid dealing with IDs rather than pointers
|
||||
(which requires us to impose an ordering to get meaningful equality).
|
||||
@item
|
||||
The region model code doesn't yet support @code{memcpy}. At the
|
||||
gimple-ssa level these have been optimized to statements like this:
|
||||
@smallexample
|
||||
_10 = MEM <long unsigned int> [(char * @{ref-all@})&c]
|
||||
MEM <long unsigned int> [(char * @{ref-all@})&d] = _10;
|
||||
@end smallexample
|
||||
Perhaps they could be supported via a new @code{compound_svalue} type.
|
||||
@item
|
||||
There are various other limitations in the region model (grep for TODO/xfail
|
||||
in the testsuite).
|
||||
@item
|
||||
The constraint_manager's implementation of transitivity is currently too
|
||||
expensive to enable by default and so must be manually enabled via
|
||||
@option{-fanalyzer-transitivity}).
|
||||
@item
|
||||
The checkers are currently hardcoded and don't allow for user extensibility
|
||||
(e.g. adding allocate/release pairs).
|
||||
@item
|
||||
Although the analyzer's test suite has a proof-of-concept test case for
|
||||
LTO, LTO support hasn't had extensive testing. There are various
|
||||
lang-specific things in the analyzer that assume C rather than LTO.
|
||||
For example, SSA names are printed to the user in ``raw'' form, rather
|
||||
than printing the underlying variable name.
|
||||
@end itemize
|
||||
|
||||
Some ideas for other checkers
|
||||
@itemize @bullet
|
||||
@item
|
||||
File-descriptor-based APIs
|
||||
@item
|
||||
Linux kernel internal APIs
|
||||
@item
|
||||
Signal handling
|
||||
@end itemize
|
||||
|
||||
@node Debugging the Analyzer
|
||||
@section Debugging the Analyzer
|
||||
@cindex analyzer, debugging
|
||||
@cindex static analyzer, debugging
|
||||
|
||||
@subsection Special Functions for Debugging the Analyzer
|
||||
|
||||
The analyzer recognizes various special functions by name, for use
|
||||
in debugging the analyzer. Declarations can be seen in the testsuite
|
||||
in @file{analyzer-decls.h}. None of these functions are actually
|
||||
implemented.
|
||||
|
||||
Add:
|
||||
@smallexample
|
||||
__analyzer_break ();
|
||||
@end smallexample
|
||||
to the source being analyzed to trigger a breakpoint in the analyzer when
|
||||
that source is reached. By putting a series of these in the source, it's
|
||||
much easier to effectively step through the program state as it's analyzed.
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump ();
|
||||
@end smallexample
|
||||
|
||||
will dump the copious information about the analyzer's state each time it
|
||||
reaches the call in its traversal of the source.
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump_path ();
|
||||
@end smallexample
|
||||
|
||||
will emit a placeholder ``note'' diagnostic with a path to that call site,
|
||||
if the analyzer finds a feasible path to it.
|
||||
|
||||
The builtin @code{__analyzer_dump_exploded_nodes} will dump information
|
||||
after analysis on all of the exploded nodes at that program point:
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump_exploded_nodes (0);
|
||||
@end smallexample
|
||||
|
||||
will dump just the number of nodes, and their IDs.
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump_exploded_nodes (1);
|
||||
@end smallexample
|
||||
|
||||
will also dump all of the states within those nodes.
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump_region_model ();
|
||||
@end smallexample
|
||||
will dump the region_model's state to stderr.
|
||||
|
||||
@smallexample
|
||||
__analyzer_eval (expr);
|
||||
@end smallexample
|
||||
will emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the
|
||||
truthfulness of the argument. This is useful for writing DejaGnu tests.
|
||||
|
||||
|
||||
@subsection Other Debugging Techniques
|
||||
|
||||
One approach when tracking down where a particular bogus state is
|
||||
introduced into the @code{exploded_graph} is to add custom code to
|
||||
@code{region_model::validate}.
|
||||
|
||||
For example, this custom code (added to @code{region_model::validate})
|
||||
breaks with an assertion failure when a variable called @code{ptr}
|
||||
acquires a value that's unknown, using
|
||||
@code{region_model::get_value_by_name} to locate the variable
|
||||
|
||||
@smallexample
|
||||
/* Find a variable matching "ptr". */
|
||||
svalue_id sid = get_value_by_name ("ptr");
|
||||
if (!sid.null_p ())
|
||||
@{
|
||||
svalue *sval = get_svalue (sid);
|
||||
gcc_assert (sval->get_kind () != SK_UNKNOWN);
|
||||
@}
|
||||
@end smallexample
|
||||
|
||||
making it easier to investigate further in a debugger when this occurs.
|
|
@ -125,6 +125,7 @@ Additional tutorial information is linked to from
|
|||
* LTO:: Using Link-Time Optimization.
|
||||
|
||||
* Match and Simplify:: How to write expression simplification patterns for GIMPLE and GENERIC
|
||||
* Static Analyzer:: Working with the static analyzer.
|
||||
* User Experience Guidelines:: Guidelines for implementing diagnostics and options.
|
||||
* Funding:: How to help assure funding for free software.
|
||||
* GNU Project:: The GNU Project and GNU/Linux.
|
||||
|
@ -163,6 +164,7 @@ Additional tutorial information is linked to from
|
|||
@include plugins.texi
|
||||
@include lto.texi
|
||||
@include match-and-simplify.texi
|
||||
@include analyzer.texi
|
||||
@include ux.texi
|
||||
|
||||
@include funding.texi
|
||||
|
|
|
@ -153,6 +153,7 @@ listing and explanation of the binary and decimal byte size prefixes.
|
|||
* Diagnostic Message Formatting Options:: Controlling how diagnostics should
|
||||
be formatted.
|
||||
* Warning Options:: How picky should the compiler be?
|
||||
* Static Analyzer Options:: More expensive warnings.
|
||||
* Debugging Options:: Producing debuggable code.
|
||||
* Optimize Options:: How much optimization?
|
||||
* Instrumentation Options:: Enabling profiling and extra run-time error checking.
|
||||
|
@ -287,13 +288,31 @@ Objective-C and Objective-C++ Dialects}.
|
|||
|
||||
@item Warning Options
|
||||
@xref{Warning Options,,Options to Request or Suppress Warnings}.
|
||||
@gccoptlist{-fsyntax-only -fmax-errors=@var{n} -Wpedantic @gol
|
||||
@gccoptlist{-fanalyzer -fsyntax-only -fmax-errors=@var{n} -Wpedantic @gol
|
||||
-pedantic-errors @gol
|
||||
-w -Wextra -Wall -Waddress -Waddress-of-packed-member @gol
|
||||
-Waggregate-return -Waligned-new @gol
|
||||
-Walloc-zero -Walloc-size-larger-than=@var{byte-size} @gol
|
||||
-Walloca -Walloca-larger-than=@var{byte-size} @gol
|
||||
-Wno-aggressive-loop-optimizations -Warray-bounds -Warray-bounds=@var{n} @gol
|
||||
-Wno-aggressive-loop-optimizations @gol
|
||||
-Wno-analyzer-double-fclose @gol
|
||||
-Wno-analyzer-double-free @gol
|
||||
-Wno-analyzer-exposure-through-output-file @gol
|
||||
-Wno-analyzer-file-leak @gol
|
||||
-Wno-analyzer-free-of-non-heap @gol
|
||||
-Wno-analyzer-malloc-leak @gol
|
||||
-Wno-analyzer-possible-null-argument @gol
|
||||
-Wno-analyzer-possible-null-dereference @gol
|
||||
-Wno-analyzer-null-argument @gol
|
||||
-Wno-analyzer-null-dereference @gol
|
||||
-Wno-analyzer-stale-setjmp-buffer @gol
|
||||
-Wno-analyzer-tainted-array-index @gol
|
||||
-Wno-analyzer-unsafe-call-within-signal-handler @gol
|
||||
-Wno-analyzer-use-after-free @gol
|
||||
-Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol
|
||||
-Wno-analyzer-use-of-uninitialized-value @gol
|
||||
-Wanalyzer-too-complex @gol
|
||||
-Warray-bounds -Warray-bounds=@var{n} @gol
|
||||
-Wno-attributes -Wattribute-alias=@var{n} @gol
|
||||
-Wbool-compare -Wbool-operation @gol
|
||||
-Wno-builtin-declaration-mismatch @gol
|
||||
|
@ -375,6 +394,44 @@ Objective-C and Objective-C++ Dialects}.
|
|||
-Wwrite-strings @gol
|
||||
-Wzero-as-null-pointer-constant}
|
||||
|
||||
@item Static Analyzer Options
|
||||
@gccoptlist{-Wanalyzer-double-fclose @gol
|
||||
-Wanalyzer-double-free @gol
|
||||
-Wanalyzer-exposure-through-output-file @gol
|
||||
-Wanalyzer-file-leak @gol
|
||||
-Wanalyzer-free-of-non-heap @gol
|
||||
-Wanalyzer-malloc-leak @gol
|
||||
-Wanalyzer-null-argument @gol
|
||||
-Wanalyzer-null-dereference @gol
|
||||
-Wanalyzer-possible-null-argument @gol
|
||||
-Wanalyzer-possible-null-dereference @gol
|
||||
-Wanalyzer-stale-setjmp-buffer @gol
|
||||
-Wanalyzer-tainted-array-index @gol
|
||||
-Wanalyzer-unsafe-call-within-signal-handler @gol
|
||||
-Wanalyzer-use-after-free @gol
|
||||
-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
|
||||
-Wanalyzer-use-of-uninitialized-value @gol
|
||||
-Wanalyzer-too-complex @gol
|
||||
-fanalyzer-call-summaries @gol
|
||||
-fanalyzer-checker=@var{name} @gol
|
||||
-fanalyzer-fine-grained @gol
|
||||
-fanalyzer-state-merge @gol
|
||||
-fanalyzer-state-purge @gol
|
||||
-fanalyzer-transitivity @gol
|
||||
-fanalyzer-verbose-edges @gol
|
||||
-fanalyzer-verbose-state-changes @gol
|
||||
-fanalyzer-verbosity=@var{level} @gol
|
||||
-fdump-analyzer @gol
|
||||
-fdump-analyzer-stderr @gol
|
||||
-fdump-analyzer-callgraph @gol
|
||||
-fdump-analyzer-exploded-graph @gol
|
||||
-fdump-analyzer-exploded-nodes @gol
|
||||
-fdump-analyzer-exploded-nodes-2 @gol
|
||||
-fdump-analyzer-exploded-nodes-3 @gol
|
||||
-fdump-analyzer-state-purge @gol
|
||||
-fdump-analyzer-supergraph @gol
|
||||
}
|
||||
|
||||
@item C and Objective-C-only Warning Options
|
||||
@gccoptlist{-Wbad-function-cast -Wmissing-declarations @gol
|
||||
-Wmissing-parameter-type -Wmissing-prototypes -Wnested-externs @gol
|
||||
|
@ -6412,6 +6469,169 @@ See also @option{-Wvla-larger-than=}@samp{byte-size}.
|
|||
Disable @option{-Walloca-larger-than=} warnings. The option is
|
||||
equivalent to @option{-Walloca-larger-than=}@samp{SIZE_MAX} or larger.
|
||||
|
||||
@item -Wno-analyzer-double-fclose
|
||||
@opindex Wanalyzer-double-fclose
|
||||
@opindex Wno-analyzer-double-fclose
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-double-fclose} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a @code{FILE *}
|
||||
can have @code{fclose} called on it more than once.
|
||||
|
||||
@item -Wno-analyzer-double-free
|
||||
@opindex Wanalyzer-double-free
|
||||
@opindex Wno-analyzer-double-free
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-double-free} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a pointer
|
||||
can have @code{free} called on it more than once.
|
||||
|
||||
@item -Wno-analyzer-exposure-through-output-file
|
||||
@opindex Wanalyzer-exposure-through-output-file
|
||||
@opindex Wno-analyzer-exposure-through-output-file
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-exposure-through-output-file}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
security-sensitive value is written to an output file
|
||||
(such as writing a password to a log file).
|
||||
|
||||
@item -Wno-analyzer-file-leak
|
||||
@opindex Wanalyzer-file-leak
|
||||
@opindex Wno-analyzer-file-leak
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-file-leak}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
@code{<stdio.h>} @code{FILE *} stream object is leaked.
|
||||
|
||||
@item -Wno-analyzer-free-of-non-heap
|
||||
@opindex Wanalyzer-free-of-non-heap
|
||||
@opindex Wno-analyzer-free-of-non-heap
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-free-of-non-heap}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which @code{free}
|
||||
is called on a non-heap pointer (e.g. an on-stack buffer, or a global).
|
||||
|
||||
@item -Wno-analyzer-malloc-leak
|
||||
@opindex Wanalyzer-malloc-leak
|
||||
@opindex Wno-analyzer-malloc-leak
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-malloc-leak}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
pointer allocated via @code{malloc} is leaked.
|
||||
|
||||
@item -Wno-analyzer-possible-null-argument
|
||||
@opindex Wanalyzer-possible-null-argument
|
||||
@opindex Wno-analyzer-possible-null-argument
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-possible-null-argument} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
possibly-NULL value is passed to a function argument marked
|
||||
with @code{__attribute__((nonnull))} as requiring a non-NULL
|
||||
value.
|
||||
|
||||
@item -Wno-analyzer-possible-null-dereference
|
||||
@opindex Wanalyzer-possible-null-dereference
|
||||
@opindex Wno-analyzer-possible-null-dereference
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-possible-null-dereference} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
possibly-NULL value is dereferenced.
|
||||
|
||||
@item -Wno-analyzer-null-argument
|
||||
@opindex Wanalyzer-null-argument
|
||||
@opindex Wno-analyzer-null-argument
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-null-argument} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
value known to be NULL is passed to a function argument marked
|
||||
with @code{__attribute__((nonnull))} as requiring a non-NULL
|
||||
value.
|
||||
|
||||
@item -Wno-analyzer-null-dereference
|
||||
@opindex Wanalyzer-null-dereference
|
||||
@opindex Wno-analyzer-null-dereference
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-null-dereference} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
value known to be NULL is dereferenced.
|
||||
|
||||
@item -Wno-analyzer-stale-setjmp-buffer
|
||||
@opindex Wanalyzer-stale-setjmp-buffer
|
||||
@opindex Wno-analyzer-stale-setjmp-buffer
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-stale-setjmp-buffer} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which
|
||||
@code{longjmp} is called to rewind to a @code{jmp_buf} relating
|
||||
to a @code{setjmp} call in a function that has returned.
|
||||
|
||||
When @code{setjmp} is called on a @code{jmp_buf} to record a rewind
|
||||
location, it records the stack frame. The stack frame becomes invalid
|
||||
when the function containing the @code{setjmp} call returns. Attempting
|
||||
to rewind to it via @code{longjmp} would reference a stack frame that
|
||||
no longer exists, and likely lead to a crash (or worse).
|
||||
|
||||
@item -Wno-analyzer-tainted-array-index
|
||||
@opindex Wanalyzer-tainted-array-index
|
||||
@opindex Wno-analyzer-tainted-array-index
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-tainted-array-index} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a value
|
||||
that could be under an attacker's control is used as the index
|
||||
of an array access without being sanitized.
|
||||
|
||||
@item -Wno-analyzer-unsafe-call-within-signal-handler
|
||||
@opindex Wanalyzer-unsafe-call-within-signal-handler
|
||||
@opindex Wno-analyzer-unsafe-call-within-signal-handler
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-unsafe-call-within-signal-handler} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
function known to be async-signal-unsafe (such as @code{fprintf}) is
|
||||
called from a signal handler.
|
||||
|
||||
@item -Wno-analyzer-use-after-free
|
||||
@opindex Wanalyzer-use-after-free
|
||||
@opindex Wno-analyzer-use-after-free
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-use-after-free} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a
|
||||
pointer is used after @code{free} is called on it.
|
||||
|
||||
@item -Wno-analyzer-use-of-pointer-in-stale-stack-frame
|
||||
@opindex Wanalyzer-use-of-pointer-in-stale-stack-frame
|
||||
@opindex Wno-analyzer-use-of-pointer-in-stale-stack-frame
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-use-of-pointer-in-stale-stack-frame}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which a pointer
|
||||
is dereferenced that points to a variable in a stale stack frame.
|
||||
|
||||
@item -Wno-analyzer-use-of-uninitialized-value
|
||||
@opindex Wanalyzer-use-of-uninitialized-value
|
||||
@opindex Wno-analyzer-use-of-uninitialized-value
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-use-of-uninitialized-value} to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which an uninitialized
|
||||
value is used.
|
||||
|
||||
@item -Warray-bounds
|
||||
@itemx -Warray-bounds=@var{n}
|
||||
@opindex Wno-array-bounds
|
||||
|
@ -8078,6 +8298,217 @@ OpenMP construct.
|
|||
|
||||
@end table
|
||||
|
||||
@node Static Analyzer Options
|
||||
@section Options That Control Static Analysis
|
||||
|
||||
@table @gcctabopt
|
||||
@item -fanalyzer
|
||||
@opindex analyzer
|
||||
@opindex fanalyzer
|
||||
@opindex fno-analyzer
|
||||
This option enables an static analysis of program flow which looks
|
||||
for ``interesting'' interprocedural paths through the
|
||||
code, and issues warnings for problems found on them.
|
||||
|
||||
This analysis is much more expensive than other GCC warnings.
|
||||
|
||||
Enabling this option effectively enables the following warnings:
|
||||
|
||||
@gccoptlist{ @gol
|
||||
-Wanalyzer-double-fclose @gol
|
||||
-Wanalyzer-double-free @gol
|
||||
-Wanalyzer-exposure-through-output-file @gol
|
||||
-Wanalyzer-file-leak @gol
|
||||
-Wanalyzer-free-of-non-heap @gol
|
||||
-Wanalyzer-malloc-leak @gol
|
||||
-Wanalyzer-possible-null-argument @gol
|
||||
-Wanalyzer-possible-null-dereference @gol
|
||||
-Wanalyzer-null-argument @gol
|
||||
-Wanalyzer-null-dereference @gol
|
||||
-Wanalyzer-tainted-array-index @gol
|
||||
-Wanalyzer-unsafe-call-within-signal-handler @gol
|
||||
-Wanalyzer-use-after-free @gol
|
||||
-Wanalyzer-use-of-uninitialized-value @gol
|
||||
-Wanalyzer-use-of-pointer-in-stale-stack-frame @gol
|
||||
}
|
||||
|
||||
This option is only available if GCC was configured with analyzer
|
||||
support enabled.
|
||||
|
||||
@item -Wanalyzer-too-complex
|
||||
@opindex Wanalyzer-too-complex
|
||||
@opindex Wno-analyzer-too-complex
|
||||
If @option{-fanalyzer} is enabled, the analyzer uses various heuristics
|
||||
to attempt to explore the control flow and data flow in the program,
|
||||
but these can be defeated by sufficiently complicated code.
|
||||
|
||||
By default, the analysis will silently stop if the code is too
|
||||
complicated for the analyzer to fully explore and it reaches an internal
|
||||
limit.
|
||||
|
||||
The @option{-Wanalyzer-too-complex} option will warn if this occurs.
|
||||
|
||||
@end table
|
||||
|
||||
Pertinent parameters for controlling the exploration are:
|
||||
@option{--param analyzer-bb-explosion-factor=@var{value}},
|
||||
@option{--param analyzer-max-enodes-per-program-point=@var{value}},
|
||||
@option{--param analyzer-max-recursion-depth=@var{value}}, and
|
||||
@option{--param analyzer-min-snodes-for-call-summary=@var{value}}.
|
||||
|
||||
The following options control the analyzer.
|
||||
|
||||
@table @gcctabopt
|
||||
|
||||
@item -fanalyzer-call-summaries
|
||||
@opindex fanalyzer-call-summaries
|
||||
@opindex fno-analyzer-call-summaries
|
||||
Simplify interprocedural analysis by computing the effect of certain calls,
|
||||
rather than exploring all paths through the function from callsite to each
|
||||
possible return.
|
||||
|
||||
If enabled, call summaries are only used for functions with more than one
|
||||
call site, and that are sufficiently complicated (as per
|
||||
@option{--param analyzer-min-snodes-for-call-summary=@var{value}}).
|
||||
|
||||
@item -fanalyzer-checker=@var{name}
|
||||
@opindex fanalyzer-checker
|
||||
Restrict the analyzer to run just the named checker.
|
||||
|
||||
@item -fanalyzer-fine-grained
|
||||
@opindex fanalyzer-fine-grained
|
||||
@opindex fno-analyzer-fine-grained
|
||||
This option is intended for analyzer developers.
|
||||
|
||||
Internally the analyzer builds an ``exploded graph'' that combines
|
||||
control flow graphs with data flow information.
|
||||
|
||||
By default, an edge in this graph can contain the effects of a run
|
||||
of multiple statements within a basic block. With
|
||||
@option{-fanalyzer-fine-grained}, each statement gets its own edge.
|
||||
|
||||
@item -fno-analyzer-state-merge
|
||||
@opindex fanalyzer-state-merge
|
||||
@opindex fno-analyzer-state-merge
|
||||
This option is intended for analyzer developers.
|
||||
|
||||
By default the analyzer will attempt to simplify analysis by merging
|
||||
sufficiently similar states at each program point as it builds its
|
||||
``exploded graph''. With @option{-fno-analyzer-state-merge} this
|
||||
merging can be suppressed, for debugging state-handling issues.
|
||||
|
||||
@item -fno-analyzer-state-purge
|
||||
@opindex fanalyzer-state-purge
|
||||
@opindex fno-analyzer-state-purge
|
||||
This option is intended for analyzer developers.
|
||||
|
||||
By default the analyzer will attempt to simplify analysis by purging
|
||||
aspects of state at a program point that appear to no longer be relevant
|
||||
e.g. the values of locals that aren't accessed later in the function
|
||||
and which aren't relevant to leak analysis.
|
||||
|
||||
With @option{-fno-analyzer-state-purge} this purging of state can
|
||||
be suppressed, for debugging state-handling issues.
|
||||
|
||||
@item -fanalyzer-transitivity
|
||||
@opindex fanalyzer-transitivity
|
||||
@opindex fno-analyzer-transitivity
|
||||
This option enables transitivity of constraints within the analyzer.
|
||||
|
||||
@item -fanalyzer-verbose-edges
|
||||
This option is intended for analyzer developers. It enables more
|
||||
verbose, lower-level detail in the descriptions of control flow
|
||||
within diagnostic paths.
|
||||
|
||||
@item -fanalyzer-verbose-state-changes
|
||||
This option is intended for analyzer developers. It enables more
|
||||
verbose, lower-level detail in the descriptions of events relating
|
||||
to state machines within diagnostic paths.
|
||||
|
||||
@item -fanalyzer-verbosity=@var{level}
|
||||
This option controls the complexity of the control flow paths that are
|
||||
emitted for analyzer diagnostics.
|
||||
|
||||
The @var{level} can be one of:
|
||||
|
||||
@table @samp
|
||||
@item 0
|
||||
At this level, interprocedural call and return events are displayed,
|
||||
along with the most pertinent state-change events relating to
|
||||
a diagnostic. For example, for a double-@code{free} diagnostic,
|
||||
both calls to @code{free} will be shown.
|
||||
|
||||
@item 1
|
||||
As per the previous level, but also show events for the entry
|
||||
to each function.
|
||||
|
||||
@item 2
|
||||
As per the previous level, but also show events relating to
|
||||
control flow (e.g. ``true path taken'' at a conditional).
|
||||
|
||||
This level is the default.
|
||||
|
||||
@item 3
|
||||
This level is intended for analyzer developers; it adds various
|
||||
other events intended for debugging the analyzer.
|
||||
|
||||
@end table
|
||||
|
||||
@item -fdump-analyzer
|
||||
@opindex fdump-analyzer
|
||||
Dump internal details about what the analyzer is doing to
|
||||
@file{@var{file}.analyzer.txt}.
|
||||
This option is overridden by @option{-fdump-analyzer-stderr}.
|
||||
|
||||
@item -fdump-analyzer-stderr
|
||||
@opindex fdump-analyzer-stderr
|
||||
Dump internal details about what the analyzer is doing to stderr.
|
||||
This option overrides @option{-fdump-analyzer}.
|
||||
|
||||
@item -fdump-analyzer-callgraph
|
||||
@opindex fdump-analyzer-callgraph
|
||||
Dump a representation of the call graph suitable for viewing with
|
||||
GraphViz to @file{@var{file}.callgraph.dot}.
|
||||
|
||||
@item -fdump-analyzer-exploded-graph
|
||||
@opindex fdump-analyzer-exploded-graph
|
||||
Dump a representation of the ``exploded graph'' suitable for viewing with
|
||||
GraphViz to @file{@var{file}.eg.dot}.
|
||||
Nodes are color-coded based on state-machine states to emphasize
|
||||
state changes.
|
||||
|
||||
@item -fdump-analyzer-exploded-nodes
|
||||
@opindex dump-analyzer-exploded-nodes
|
||||
Emit diagnostics showing where nodes in the ``exploded graph'' are
|
||||
in relation to the program source.
|
||||
|
||||
@item -fdump-analyzer-exploded-nodes-2
|
||||
@opindex dump-analyzer-exploded-nodes-2
|
||||
Dump a textual representation of the ``exploded graph'' to
|
||||
@file{@var{file}.eg.txt}.
|
||||
|
||||
@item -fdump-analyzer-exploded-nodes-3
|
||||
@opindex dump-analyzer-exploded-nodes-3
|
||||
Dump a textual representation of the ``exploded graph'' to
|
||||
one dump file per node, to @file{@var{file}.eg-@var{id}.txt}.
|
||||
This is typically a large number of dump files.
|
||||
|
||||
@item -fdump-analyzer-state-purge
|
||||
@opindex fdump-analyzer-state-purge
|
||||
As per @option{-fdump-analyzer-supergraph}, dump a representation of the
|
||||
``supergraph'' suitable for viewing with GraphViz, but annotate the
|
||||
graph with information on what state will be purged at each node.
|
||||
The graph is written to @file{@var{file}.state-purge.dot}.
|
||||
|
||||
@item -fdump-analyzer-supergraph
|
||||
@opindex fdump-analyzer-supergraph
|
||||
Dump a representation of the ``supergraph'' suitable for viewing with
|
||||
GraphViz to @file{@var{file}.supergraph.dot}. This shows all of the
|
||||
control flow graphs in the program, with interprocedural edges for
|
||||
calls and returns.
|
||||
|
||||
@end table
|
||||
|
||||
@node Debugging Options
|
||||
@section Options for Debugging Your Program
|
||||
@cindex options, debugging
|
||||
|
|
|
@ -2644,6 +2644,9 @@ This is equivalent to @code{dg-require-effective-target cxa_atexit}.
|
|||
@item dg-require-dll ""
|
||||
Skip the test if the target does not support DLL attributes.
|
||||
|
||||
@item dg-require-dot ""
|
||||
Skip the test if the host does not have @command{dot}.
|
||||
|
||||
@item dg-require-fork ""
|
||||
Skip the test if the target does not support @code{fork}.
|
||||
|
||||
|
@ -2687,6 +2690,9 @@ Passes if @var{regexp} matches text in @var{filename}.
|
|||
Passes if @var{regexp} does not match text in @var{filename}.
|
||||
@item scan-module @var{module} @var{regexp} [@{ target/xfail @var{selector} @}]
|
||||
Passes if @var{regexp} matches in Fortran module @var{module}.
|
||||
@item dg-check-dot @var{filename}
|
||||
Passes if @var{filename} is a valid @file{.dot} file (by running
|
||||
@code{dot -Tpng} on it, and verifying the exit code is 0).
|
||||
@end table
|
||||
|
||||
@subsubsection Scan the assembly output
|
||||
|
|
|
@ -219,6 +219,16 @@ is emitted (as opposed to those warnings that are suppressed by
|
|||
command-line options).
|
||||
end
|
||||
|
||||
define break-on-saved-diagnostic
|
||||
break diagnostic_manager::add_diagnostic
|
||||
end
|
||||
|
||||
document break-on-saved-diagnostic
|
||||
Put a breakpoint on diagnostic_manager::add_diagnostic, called within
|
||||
the analyzer whenever a diagnostic is saved for later de-duplication and
|
||||
possible emission.
|
||||
end
|
||||
|
||||
define reload-gdbhooks
|
||||
python import imp; imp.reload(gdbhooks)
|
||||
end
|
||||
|
|
100
gcc/graphviz.cc
Normal file
100
gcc/graphviz.cc
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* Helper code for graphviz output.
|
||||
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 "graphviz.h"
|
||||
|
||||
/* graphviz_out's ctor, wrapping PP. */
|
||||
|
||||
graphviz_out::graphviz_out (pretty_printer *pp)
|
||||
: m_pp (pp),
|
||||
m_indent (0)
|
||||
{
|
||||
}
|
||||
|
||||
/* Formatted print of FMT. */
|
||||
|
||||
void
|
||||
graphviz_out::print (const char *fmt, ...)
|
||||
{
|
||||
text_info text;
|
||||
va_list ap;
|
||||
|
||||
va_start (ap, fmt);
|
||||
text.err_no = errno;
|
||||
text.args_ptr = ≈
|
||||
text.format_spec = fmt;
|
||||
pp_format (m_pp, &text);
|
||||
pp_output_formatted_text (m_pp);
|
||||
va_end (ap);
|
||||
}
|
||||
|
||||
/* Formatted print of FMT. The text is indented by the current
|
||||
indent, and a newline is added. */
|
||||
|
||||
void
|
||||
graphviz_out::println (const char *fmt, ...)
|
||||
{
|
||||
text_info text;
|
||||
va_list ap;
|
||||
|
||||
write_indent ();
|
||||
|
||||
va_start (ap, fmt);
|
||||
text.err_no = errno;
|
||||
text.args_ptr = ≈
|
||||
text.format_spec = fmt;
|
||||
pp_format (m_pp, &text);
|
||||
pp_output_formatted_text (m_pp);
|
||||
va_end (ap);
|
||||
|
||||
pp_newline (m_pp);
|
||||
}
|
||||
|
||||
/* Print the current indent to the underlying pp. */
|
||||
|
||||
void
|
||||
graphviz_out::write_indent ()
|
||||
{
|
||||
for (int i = 0; i < m_indent * 2; ++i)
|
||||
pp_space (m_pp);
|
||||
}
|
||||
|
||||
/* Write the start of an HTML-like row via <TR><TD>, writing to the stream
|
||||
so that followup text can be escaped. */
|
||||
|
||||
void
|
||||
graphviz_out::begin_tr ()
|
||||
{
|
||||
pp_string (m_pp, "<TR><TD ALIGN=\"LEFT\">");
|
||||
pp_write_text_to_stream (m_pp);
|
||||
}
|
||||
|
||||
/* Write the end of an HTML-like row via </TD></TR>, writing to the stream
|
||||
so that followup text can be escaped. */
|
||||
|
||||
void
|
||||
graphviz_out::end_tr ()
|
||||
{
|
||||
pp_string (m_pp, "</TD></TR>");
|
||||
pp_write_text_to_stream (m_pp);
|
||||
}
|
53
gcc/graphviz.h
Normal file
53
gcc/graphviz.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* Helper code for graphviz output.
|
||||
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_GRAPHVIZ_H
|
||||
#define GCC_GRAPHVIZ_H
|
||||
|
||||
#include "pretty-print.h" /* for ATTRIBUTE_GCC_PPDIAG. */
|
||||
|
||||
/* A class for writing .dot output to a pretty_printer with
|
||||
indentation to show nesting. */
|
||||
|
||||
class graphviz_out {
|
||||
public:
|
||||
graphviz_out (pretty_printer *pp);
|
||||
|
||||
void print (const char *fmt, ...)
|
||||
ATTRIBUTE_GCC_PPDIAG(2,3);
|
||||
void println (const char *fmt, ...)
|
||||
ATTRIBUTE_GCC_PPDIAG(2,3);
|
||||
|
||||
void indent () { m_indent++; }
|
||||
void outdent () { m_indent--; }
|
||||
|
||||
void write_indent ();
|
||||
|
||||
void begin_tr ();
|
||||
void end_tr ();
|
||||
|
||||
pretty_printer *get_pp () const { return m_pp; }
|
||||
|
||||
private:
|
||||
pretty_printer *m_pp;
|
||||
int m_indent;
|
||||
};
|
||||
|
||||
#endif /* GCC_GRAPHVIZ_H */
|
247
gcc/ordered-hash-map-tests.cc
Normal file
247
gcc/ordered-hash-map-tests.cc
Normal file
|
@ -0,0 +1,247 @@
|
|||
/* Unit tests for ordered-hash-map.h.
|
||||
Copyright (C) 2015-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/>. */
|
||||
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include "coretypes.h"
|
||||
#include "tm.h"
|
||||
#include "opts.h"
|
||||
#include "hash-set.h"
|
||||
#include "fixed-value.h"
|
||||
#include "alias.h"
|
||||
#include "flags.h"
|
||||
#include "symtab.h"
|
||||
#include "tree-core.h"
|
||||
#include "stor-layout.h"
|
||||
#include "tree.h"
|
||||
#include "stringpool.h"
|
||||
#include "ordered-hash-map.h"
|
||||
#include "selftest.h"
|
||||
|
||||
#if CHECKING_P
|
||||
|
||||
namespace selftest {
|
||||
|
||||
/* Populate *OUT_KVS with the key/value pairs of M. */
|
||||
|
||||
template <typename HashMap, typename Key, typename Value>
|
||||
static void
|
||||
get_kv_pairs (const HashMap &m,
|
||||
auto_vec<std::pair<Key, Value> > *out_kvs)
|
||||
{
|
||||
for (typename HashMap::iterator iter = m.begin ();
|
||||
iter != m.end ();
|
||||
++iter)
|
||||
out_kvs->safe_push (std::make_pair ((*iter).first, (*iter).second));
|
||||
}
|
||||
|
||||
/* Construct an ordered_hash_map <const char *, int> and verify that
|
||||
various operations work correctly. */
|
||||
|
||||
static void
|
||||
test_map_of_strings_to_int ()
|
||||
{
|
||||
ordered_hash_map <const char *, int> m;
|
||||
|
||||
const char *ostrich = "ostrich";
|
||||
const char *elephant = "elephant";
|
||||
const char *ant = "ant";
|
||||
const char *spider = "spider";
|
||||
const char *millipede = "Illacme plenipes";
|
||||
const char *eric = "half a bee";
|
||||
|
||||
/* A fresh hash_map should be empty. */
|
||||
ASSERT_EQ (0, m.elements ());
|
||||
ASSERT_EQ (NULL, m.get (ostrich));
|
||||
|
||||
/* Populate the hash_map. */
|
||||
ASSERT_EQ (false, m.put (ostrich, 2));
|
||||
ASSERT_EQ (false, m.put (elephant, 4));
|
||||
ASSERT_EQ (false, m.put (ant, 6));
|
||||
ASSERT_EQ (false, m.put (spider, 8));
|
||||
ASSERT_EQ (false, m.put (millipede, 750));
|
||||
ASSERT_EQ (false, m.put (eric, 3));
|
||||
|
||||
/* Verify that we can recover the stored values. */
|
||||
ASSERT_EQ (6, m.elements ());
|
||||
ASSERT_EQ (2, *m.get (ostrich));
|
||||
ASSERT_EQ (4, *m.get (elephant));
|
||||
ASSERT_EQ (6, *m.get (ant));
|
||||
ASSERT_EQ (8, *m.get (spider));
|
||||
ASSERT_EQ (750, *m.get (millipede));
|
||||
ASSERT_EQ (3, *m.get (eric));
|
||||
|
||||
/* Verify that the order of insertion is preserved. */
|
||||
auto_vec<std::pair<const char *, int> > kvs;
|
||||
get_kv_pairs (m, &kvs);
|
||||
ASSERT_EQ (kvs.length (), 6);
|
||||
ASSERT_EQ (kvs[0].first, ostrich);
|
||||
ASSERT_EQ (kvs[0].second, 2);
|
||||
ASSERT_EQ (kvs[1].first, elephant);
|
||||
ASSERT_EQ (kvs[1].second, 4);
|
||||
ASSERT_EQ (kvs[2].first, ant);
|
||||
ASSERT_EQ (kvs[2].second, 6);
|
||||
ASSERT_EQ (kvs[3].first, spider);
|
||||
ASSERT_EQ (kvs[3].second, 8);
|
||||
ASSERT_EQ (kvs[4].first, millipede);
|
||||
ASSERT_EQ (kvs[4].second, 750);
|
||||
ASSERT_EQ (kvs[5].first, eric);
|
||||
ASSERT_EQ (kvs[5].second, 3);
|
||||
}
|
||||
|
||||
/* Construct an ordered_hash_map using int_hash and verify that various
|
||||
operations work correctly. */
|
||||
|
||||
static void
|
||||
test_map_of_int_to_strings ()
|
||||
{
|
||||
const int EMPTY = -1;
|
||||
const int DELETED = -2;
|
||||
typedef int_hash <int, EMPTY, DELETED> int_hash_t;
|
||||
ordered_hash_map <int_hash_t, const char *> m;
|
||||
|
||||
const char *ostrich = "ostrich";
|
||||
const char *elephant = "elephant";
|
||||
const char *ant = "ant";
|
||||
const char *spider = "spider";
|
||||
const char *millipede = "Illacme plenipes";
|
||||
const char *eric = "half a bee";
|
||||
|
||||
/* A fresh hash_map should be empty. */
|
||||
ASSERT_EQ (0, m.elements ());
|
||||
ASSERT_EQ (NULL, m.get (2));
|
||||
|
||||
/* Populate the hash_map. */
|
||||
ASSERT_EQ (false, m.put (2, ostrich));
|
||||
ASSERT_EQ (false, m.put (4, elephant));
|
||||
ASSERT_EQ (false, m.put (6, ant));
|
||||
ASSERT_EQ (false, m.put (8, spider));
|
||||
ASSERT_EQ (false, m.put (750, millipede));
|
||||
ASSERT_EQ (false, m.put (3, eric));
|
||||
|
||||
/* Verify that we can recover the stored values. */
|
||||
ASSERT_EQ (6, m.elements ());
|
||||
ASSERT_EQ (*m.get (2), ostrich);
|
||||
ASSERT_EQ (*m.get (4), elephant);
|
||||
ASSERT_EQ (*m.get (6), ant);
|
||||
ASSERT_EQ (*m.get (8), spider);
|
||||
ASSERT_EQ (*m.get (750), millipede);
|
||||
ASSERT_EQ (*m.get (3), eric);
|
||||
|
||||
/* Verify that the order of insertion is preserved. */
|
||||
auto_vec<std::pair<int, const char *> > kvs;
|
||||
get_kv_pairs (m, &kvs);
|
||||
ASSERT_EQ (kvs.length (), 6);
|
||||
ASSERT_EQ (kvs[0].first, 2);
|
||||
ASSERT_EQ (kvs[0].second, ostrich);
|
||||
ASSERT_EQ (kvs[1].first, 4);
|
||||
ASSERT_EQ (kvs[1].second, elephant);
|
||||
ASSERT_EQ (kvs[2].first, 6);
|
||||
ASSERT_EQ (kvs[2].second, ant);
|
||||
ASSERT_EQ (kvs[3].first, 8);
|
||||
ASSERT_EQ (kvs[3].second, spider);
|
||||
ASSERT_EQ (kvs[4].first, 750);
|
||||
ASSERT_EQ (kvs[4].second, millipede);
|
||||
ASSERT_EQ (kvs[5].first, 3);
|
||||
ASSERT_EQ (kvs[5].second, eric);
|
||||
}
|
||||
|
||||
/* Verify that we can remove items from an ordered_hash_map. */
|
||||
|
||||
static void
|
||||
test_removal ()
|
||||
{
|
||||
ordered_hash_map <const char *, int> m;
|
||||
|
||||
const char *ostrich = "ostrich";
|
||||
ASSERT_EQ (false, m.put (ostrich, 2));
|
||||
|
||||
ASSERT_EQ (1, m.elements ());
|
||||
ASSERT_EQ (2, *m.get (ostrich));
|
||||
|
||||
{
|
||||
auto_vec<std::pair<const char *, int> > kvs;
|
||||
get_kv_pairs (m, &kvs);
|
||||
ASSERT_EQ (kvs.length (), 1);
|
||||
ASSERT_EQ (kvs[0].first, ostrich);
|
||||
ASSERT_EQ (kvs[0].second, 2);
|
||||
}
|
||||
|
||||
m.remove (ostrich);
|
||||
|
||||
ASSERT_EQ (0, m.elements ());
|
||||
{
|
||||
auto_vec<std::pair<const char *, int> > kvs;
|
||||
get_kv_pairs (m, &kvs);
|
||||
ASSERT_EQ (kvs.length (), 0);
|
||||
}
|
||||
|
||||
/* Reinsertion (with a different value). */
|
||||
ASSERT_EQ (false, m.put (ostrich, 42));
|
||||
ASSERT_EQ (1, m.elements ());
|
||||
ASSERT_EQ (42, *m.get (ostrich));
|
||||
{
|
||||
auto_vec<std::pair<const char *, int> > kvs;
|
||||
get_kv_pairs (m, &kvs);
|
||||
ASSERT_EQ (kvs.length (), 1);
|
||||
ASSERT_EQ (kvs[0].first, ostrich);
|
||||
ASSERT_EQ (kvs[0].second, 42);
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify that ordered_hash_map's copy-ctor works. */
|
||||
|
||||
static void
|
||||
test_copy_ctor ()
|
||||
{
|
||||
ordered_hash_map <const char *, int> m;
|
||||
|
||||
const char *ostrich = "ostrich";
|
||||
ASSERT_EQ (false, m.put (ostrich, 2));
|
||||
|
||||
ASSERT_EQ (1, m.elements ());
|
||||
ASSERT_EQ (2, *m.get (ostrich));
|
||||
|
||||
ordered_hash_map <const char *, int> copy (m);
|
||||
ASSERT_EQ (1, copy.elements ());
|
||||
ASSERT_EQ (2, *copy.get (ostrich));
|
||||
|
||||
/* Remove from source. */
|
||||
m.remove (ostrich);
|
||||
ASSERT_EQ (0, m.elements ());
|
||||
|
||||
/* Copy should be unaffected. */
|
||||
ASSERT_EQ (1, copy.elements ());
|
||||
ASSERT_EQ (2, *copy.get (ostrich));
|
||||
}
|
||||
|
||||
/* Run all of the selftests within this file. */
|
||||
|
||||
void
|
||||
ordered_hash_map_tests_cc_tests ()
|
||||
{
|
||||
test_map_of_strings_to_int ();
|
||||
test_map_of_int_to_strings ();
|
||||
test_removal ();
|
||||
test_copy_ctor ();
|
||||
}
|
||||
|
||||
} // namespace selftest
|
||||
|
||||
#endif /* CHECKING_P */
|
188
gcc/ordered-hash-map.h
Normal file
188
gcc/ordered-hash-map.h
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* A type-safe hash map that retains the insertion order of keys.
|
||||
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/>. */
|
||||
|
||||
|
||||
#ifndef GCC_ORDERED_HASH_MAP_H
|
||||
#define GCC_ORDERED_HASH_MAP_H
|
||||
|
||||
/* Notes:
|
||||
- The keys must be PODs, since vec<> uses assignment to populate slots
|
||||
without properly initializing them.
|
||||
- doesn't have GTY support.
|
||||
- supports removal, but retains order of original insertion.
|
||||
(Removal might be better handled by using a doubly-linked list
|
||||
of nodes, holding the values). */
|
||||
|
||||
template<typename KeyId, typename Value,
|
||||
typename Traits>
|
||||
class ordered_hash_map
|
||||
{
|
||||
typedef typename Traits::key_type Key;
|
||||
|
||||
public:
|
||||
ordered_hash_map () {}
|
||||
|
||||
ordered_hash_map (const ordered_hash_map &other)
|
||||
: m_map (other.m_map),
|
||||
m_keys (other.m_keys.length ()),
|
||||
m_key_index (other.m_key_index)
|
||||
{
|
||||
unsigned i;
|
||||
Key key;
|
||||
FOR_EACH_VEC_ELT (other.m_keys, i, key)
|
||||
m_keys.quick_push (key);
|
||||
}
|
||||
|
||||
/* If key K isn't already in the map add key K with value V to the map, and
|
||||
return false. Otherwise set the value of the entry for key K to be V and
|
||||
return true. */
|
||||
|
||||
bool put (const Key &k, const Value &v)
|
||||
{
|
||||
bool existed = m_map.put (k, v);
|
||||
if (!existed)
|
||||
{
|
||||
bool key_present;
|
||||
int &slot = m_key_index.get_or_insert (k, &key_present);
|
||||
if (!key_present)
|
||||
{
|
||||
slot = m_keys.length ();
|
||||
m_keys.safe_push (k);
|
||||
}
|
||||
}
|
||||
return existed;
|
||||
}
|
||||
|
||||
/* If the passed in key is in the map return its value otherwise NULL. */
|
||||
|
||||
Value *get (const Key &k)
|
||||
{
|
||||
return m_map.get (k);
|
||||
}
|
||||
|
||||
/* Removing a key removes it from the map, but retains the insertion
|
||||
order. */
|
||||
|
||||
void remove (const Key &k)
|
||||
{
|
||||
m_map.remove (k);
|
||||
}
|
||||
|
||||
size_t elements () const { return m_map.elements (); }
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
explicit iterator (const ordered_hash_map &map, unsigned idx) :
|
||||
m_ordered_hash_map (map), m_idx (idx) {}
|
||||
|
||||
iterator &operator++ ()
|
||||
{
|
||||
/* Increment m_idx until we find a non-deleted element, or go beyond
|
||||
the end. */
|
||||
while (1)
|
||||
{
|
||||
++m_idx;
|
||||
if (valid_index_p ())
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Can't use std::pair here, because GCC before 4.3 don't handle
|
||||
std::pair where template parameters are references well.
|
||||
See PR86739. */
|
||||
struct reference_pair {
|
||||
const Key &first;
|
||||
Value &second;
|
||||
|
||||
reference_pair (const Key &key, Value &value)
|
||||
: first (key), second (value) {}
|
||||
|
||||
template <typename K, typename V>
|
||||
operator std::pair<K, V> () const { return std::pair<K, V> (first, second); }
|
||||
};
|
||||
|
||||
reference_pair operator* ()
|
||||
{
|
||||
const Key &k = m_ordered_hash_map.m_keys[m_idx];
|
||||
Value *slot
|
||||
= const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
|
||||
gcc_assert (slot);
|
||||
return reference_pair (k, *slot);
|
||||
}
|
||||
|
||||
bool
|
||||
operator != (const iterator &other) const
|
||||
{
|
||||
return m_idx != other.m_idx;
|
||||
}
|
||||
|
||||
/* Treat one-beyond-the-end as valid, for handling the "end" case. */
|
||||
|
||||
bool valid_index_p () const
|
||||
{
|
||||
if (m_idx > m_ordered_hash_map.m_keys.length ())
|
||||
return false;
|
||||
if (m_idx == m_ordered_hash_map.m_keys.length ())
|
||||
return true;
|
||||
const Key &k = m_ordered_hash_map.m_keys[m_idx];
|
||||
Value *slot
|
||||
= const_cast<ordered_hash_map &> (m_ordered_hash_map).get (k);
|
||||
return slot != NULL;
|
||||
}
|
||||
|
||||
const ordered_hash_map &m_ordered_hash_map;
|
||||
unsigned m_idx;
|
||||
};
|
||||
|
||||
/* Standard iterator retrieval methods. */
|
||||
|
||||
iterator begin () const
|
||||
{
|
||||
iterator i = iterator (*this, 0);
|
||||
while (!i.valid_index_p () && i != end ())
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
iterator end () const { return iterator (*this, m_keys.length ()); }
|
||||
|
||||
private:
|
||||
/* The assignment operator is not yet implemented; prevent erroneous
|
||||
usage of unsafe compiler-generated one. */
|
||||
void operator= (const ordered_hash_map &);
|
||||
|
||||
/* The underlying map. */
|
||||
hash_map<KeyId, Value, Traits> m_map;
|
||||
|
||||
/* The ordering of the keys. */
|
||||
auto_vec<Key> m_keys;
|
||||
|
||||
/* For each key that's ever been in the map, its index within m_keys. */
|
||||
hash_map<KeyId, int> m_key_index;
|
||||
};
|
||||
|
||||
/* Two-argument form. */
|
||||
|
||||
template<typename Key, typename Value,
|
||||
typename Traits = simple_hashmap_traits<default_hash_traits<Key>,
|
||||
Value> >
|
||||
class ordered_hash_map;
|
||||
|
||||
#endif /* GCC_ORDERED_HASH_MAP_H */
|
|
@ -142,6 +142,7 @@ along with GCC; see the file COPYING3. If not see
|
|||
TERMINATE_PASS_LIST (all_small_ipa_passes)
|
||||
|
||||
INSERT_PASSES_AFTER (all_regular_ipa_passes)
|
||||
NEXT_PASS (pass_analyzer);
|
||||
NEXT_PASS (pass_ipa_whole_program_visibility);
|
||||
NEXT_PASS (pass_ipa_profile);
|
||||
NEXT_PASS (pass_ipa_icf);
|
||||
|
|
|
@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "options.h"
|
||||
#include "stringpool.h"
|
||||
#include "attribs.h"
|
||||
#include "analyzer/analyzer-selftests.h"
|
||||
|
||||
/* This function needed to be split out from selftest.c as it references
|
||||
tests from the whole source tree, and so is within
|
||||
|
@ -76,6 +77,7 @@ selftest::run_tests ()
|
|||
cgraph_c_tests ();
|
||||
optinfo_emit_json_cc_tests ();
|
||||
opt_problem_cc_tests ();
|
||||
ordered_hash_map_tests_cc_tests ();
|
||||
|
||||
/* Mid-level data structures. */
|
||||
input_c_tests ();
|
||||
|
@ -85,6 +87,8 @@ selftest::run_tests ()
|
|||
gimple_c_tests ();
|
||||
rtl_tests_c_tests ();
|
||||
read_rtl_function_c_tests ();
|
||||
digraph_cc_tests ();
|
||||
tristate_cc_tests ();
|
||||
|
||||
/* Higher-level tests, or for components that other selftests don't
|
||||
rely on. */
|
||||
|
@ -114,6 +118,9 @@ selftest::run_tests ()
|
|||
/* Run any lang-specific selftests. */
|
||||
lang_hooks.run_lang_selftests ();
|
||||
|
||||
/* Run the analyzer selftests (if enabled). */
|
||||
run_analyzer_selftests ();
|
||||
|
||||
/* Force a GC at the end of the selftests, to shake out GC-related
|
||||
issues. For example, if any GC-managed items have buggy (or missing)
|
||||
finalizers, this last collection will ensure that things that were
|
||||
|
|
|
@ -228,6 +228,7 @@ extern void convert_c_tests ();
|
|||
extern void diagnostic_c_tests ();
|
||||
extern void diagnostic_format_json_cc_tests ();
|
||||
extern void diagnostic_show_locus_c_tests ();
|
||||
extern void digraph_cc_tests ();
|
||||
extern void dumpfile_c_tests ();
|
||||
extern void edit_context_c_tests ();
|
||||
extern void et_forest_c_tests ();
|
||||
|
@ -242,6 +243,7 @@ extern void input_c_tests ();
|
|||
extern void json_cc_tests ();
|
||||
extern void opt_problem_cc_tests ();
|
||||
extern void optinfo_emit_json_cc_tests ();
|
||||
extern void ordered_hash_map_tests_cc_tests ();
|
||||
extern void predict_c_tests ();
|
||||
extern void pretty_print_c_tests ();
|
||||
extern void range_tests ();
|
||||
|
@ -257,6 +259,7 @@ extern void store_merging_c_tests ();
|
|||
extern void tree_c_tests ();
|
||||
extern void tree_cfg_c_tests ();
|
||||
extern void tree_diagnostic_path_cc_tests ();
|
||||
extern void tristate_cc_tests ();
|
||||
extern void typed_splay_tree_c_tests ();
|
||||
extern void unique_ptr_tests_cc_tests ();
|
||||
extern void vec_c_tests ();
|
||||
|
|
145
gcc/shortest-paths.h
Normal file
145
gcc/shortest-paths.h
Normal file
|
@ -0,0 +1,145 @@
|
|||
/* Template class for Dijkstra's algorithm on directed graphs.
|
||||
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_SHORTEST_PATHS_H
|
||||
#define GCC_SHORTEST_PATHS_H
|
||||
|
||||
#include "timevar.h"
|
||||
|
||||
/* A record of the shortest path to each node in an graph
|
||||
from the origin node.
|
||||
The constructor runs Dijkstra's algorithm, and the results are
|
||||
stored in this class. */
|
||||
|
||||
template <typename GraphTraits, typename Path_t>
|
||||
class shortest_paths
|
||||
{
|
||||
public:
|
||||
typedef typename GraphTraits::graph_t graph_t;
|
||||
typedef typename GraphTraits::node_t node_t;
|
||||
typedef typename GraphTraits::edge_t edge_t;
|
||||
typedef Path_t path_t;
|
||||
|
||||
shortest_paths (const graph_t &graph, const node_t *origin);
|
||||
|
||||
path_t get_shortest_path (const node_t *to) const;
|
||||
|
||||
private:
|
||||
const graph_t &m_graph;
|
||||
|
||||
/* For each node (by index), the minimal distance to that node from the
|
||||
origin. */
|
||||
auto_vec<int> m_dist;
|
||||
|
||||
/* For each exploded_node (by index), the previous edge in the shortest
|
||||
path from the origin. */
|
||||
auto_vec<const edge_t *> m_prev;
|
||||
};
|
||||
|
||||
/* shortest_paths's constructor.
|
||||
|
||||
Use Dijkstra's algorithm relative to ORIGIN to populate m_dist and
|
||||
m_prev with enough information to be able to generate Path_t instances
|
||||
to give the shortest path to any node in GRAPH from ORIGIN. */
|
||||
|
||||
template <typename GraphTraits, typename Path_t>
|
||||
inline
|
||||
shortest_paths<GraphTraits, Path_t>::shortest_paths (const graph_t &graph,
|
||||
const node_t *origin)
|
||||
: m_graph (graph),
|
||||
m_dist (graph.m_nodes.length ()),
|
||||
m_prev (graph.m_nodes.length ())
|
||||
{
|
||||
auto_timevar tv (TV_ANALYZER_SHORTEST_PATHS);
|
||||
|
||||
auto_vec<int> queue (graph.m_nodes.length ());
|
||||
|
||||
for (unsigned i = 0; i < graph.m_nodes.length (); i++)
|
||||
{
|
||||
m_dist.quick_push (INT_MAX);
|
||||
m_prev.quick_push (NULL);
|
||||
queue.quick_push (i);
|
||||
}
|
||||
m_dist[origin->m_index] = 0;
|
||||
|
||||
while (queue.length () > 0)
|
||||
{
|
||||
/* Get minimal distance in queue.
|
||||
FIXME: this is O(N^2); replace with a priority queue. */
|
||||
int idx_with_min_dist = -1;
|
||||
int idx_in_queue_with_min_dist = -1;
|
||||
int min_dist = INT_MAX;
|
||||
for (unsigned i = 0; i < queue.length (); i++)
|
||||
{
|
||||
int idx = queue[i];
|
||||
if (m_dist[queue[i]] < min_dist)
|
||||
{
|
||||
min_dist = m_dist[idx];
|
||||
idx_with_min_dist = idx;
|
||||
idx_in_queue_with_min_dist = i;
|
||||
}
|
||||
}
|
||||
gcc_assert (idx_with_min_dist != -1);
|
||||
gcc_assert (idx_in_queue_with_min_dist != -1);
|
||||
|
||||
// FIXME: this is confusing: there are two indices here
|
||||
|
||||
queue.unordered_remove (idx_in_queue_with_min_dist);
|
||||
|
||||
node_t *n
|
||||
= static_cast <node_t *> (m_graph.m_nodes[idx_with_min_dist]);
|
||||
|
||||
int i;
|
||||
edge_t *succ;
|
||||
FOR_EACH_VEC_ELT (n->m_succs, i, succ)
|
||||
{
|
||||
// TODO: only for dest still in queue
|
||||
node_t *dest = succ->m_dest;
|
||||
int alt = m_dist[n->m_index] + 1;
|
||||
if (alt < m_dist[dest->m_index])
|
||||
{
|
||||
m_dist[dest->m_index] = alt;
|
||||
m_prev[dest->m_index] = succ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate an Path_t instance giving the shortest path to the node
|
||||
TO from the origin node. */
|
||||
|
||||
template <typename GraphTraits, typename Path_t>
|
||||
inline Path_t
|
||||
shortest_paths<GraphTraits, Path_t>::get_shortest_path (const node_t *to) const
|
||||
{
|
||||
Path_t result;
|
||||
|
||||
while (m_prev[to->m_index])
|
||||
{
|
||||
result.m_edges.safe_push (m_prev[to->m_index]);
|
||||
to = m_prev[to->m_index]->m_src;
|
||||
}
|
||||
|
||||
result.m_edges.reverse ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif /* GCC_SHORTEST_PATHS_H */
|
|
@ -1,3 +1,159 @@
|
|||
2020-01-14 David Malcolm <dmalcolm@redhat.com>
|
||||
|
||||
* 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.
|
||||
|
||||
2020-01-14 Jason Merrill <jason@redhat.com>
|
||||
|
||||
* lib/prune.exp (prune_gcc_output): Adjust constexpr pattern.
|
||||
|
|
30
gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
Normal file
30
gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-minimal.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
typedef struct _krb5_data {
|
||||
char *data;
|
||||
} krb5_data;
|
||||
|
||||
void
|
||||
test_1 (krb5_data inbuf, int flag)
|
||||
{
|
||||
free(inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_2 (krb5_data inbuf, int flag)
|
||||
{
|
||||
if (flag) {
|
||||
free(inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
}
|
||||
free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_3 (krb5_data inbuf, int flag)
|
||||
{
|
||||
if (flag) {
|
||||
free((char *)inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
}
|
||||
free((char *)inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
|
||||
}
|
72
gcc/testsuite/gcc.dg/analyzer/abort.c
Normal file
72
gcc/testsuite/gcc.dg/analyzer/abort.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
extern void foo ();
|
||||
extern void bar ();
|
||||
|
||||
void test_1 (int i)
|
||||
{
|
||||
if (i == 42)
|
||||
abort ();
|
||||
|
||||
__analyzer_eval (i != 42); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_2 (int i)
|
||||
{
|
||||
if (i)
|
||||
foo ();
|
||||
else
|
||||
bar ();
|
||||
|
||||
foo ();
|
||||
|
||||
if (i)
|
||||
foo ();
|
||||
else
|
||||
abort ();
|
||||
|
||||
__analyzer_eval (i != 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
void calls_abort (const char *msg)
|
||||
{
|
||||
fprintf (stderr, "%s", msg);
|
||||
abort ();
|
||||
}
|
||||
|
||||
void test_3 (void *ptr)
|
||||
{
|
||||
if (!ptr)
|
||||
calls_abort ("ptr was NULL");
|
||||
|
||||
__analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
extern void marked_noreturn (const char *msg)
|
||||
__attribute__ ((__noreturn__));
|
||||
|
||||
void test_4 (void *ptr)
|
||||
{
|
||||
if (!ptr)
|
||||
marked_noreturn ("ptr was NULL");
|
||||
|
||||
__analyzer_eval (ptr != 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
void test_5 (int i)
|
||||
{
|
||||
assert (i < 10);
|
||||
|
||||
/* We have not defined NDEBUG, so this will call __assert_fail if
|
||||
i >= 10, which is labelled with __attribute__ ((__noreturn__)). */
|
||||
__analyzer_eval (i < 10); /* { dg-warning "TRUE" } */
|
||||
}
|
8
gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
Normal file
8
gcc/testsuite/gcc.dg/analyzer/alloca-leak.c
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include <alloca.h>
|
||||
|
||||
void *test (void)
|
||||
{
|
||||
void *ptr = alloca (64);
|
||||
return ptr;
|
||||
}
|
||||
/* TODO: warn about escaping alloca. */
|
36
gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
Normal file
36
gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef ANALYZER_DECLS_H
|
||||
#define ANALYZER_DECLS_H
|
||||
|
||||
/* Function decls with special meaning to the analyzer.
|
||||
None of these are actually implemented. */
|
||||
|
||||
/* Trigger a breakpoint in the analyzer when reached. */
|
||||
extern void __analyzer_break (void);
|
||||
|
||||
/* Dump copious information about the analyzer’s state when reached. */
|
||||
extern void __analyzer_dump (void);
|
||||
|
||||
/* Dump information after analysis on all of the exploded nodes at this
|
||||
program point.
|
||||
|
||||
__analyzer_dump_exploded_nodes (0);
|
||||
will dump just the number of nodes, and their IDs.
|
||||
|
||||
__analyzer_dump_exploded_nodes (1);
|
||||
will also dump all of the states within those nodes. */
|
||||
extern void __analyzer_dump_exploded_nodes (int);
|
||||
|
||||
extern void __analyzer_dump_num_heap_regions (void);
|
||||
|
||||
/* Emit a placeholder "note" diagnostic with a path to this call site,
|
||||
if the analyzer finds a feasible path to it. */
|
||||
extern void __analyzer_dump_path (void);
|
||||
|
||||
/* Dump the region_model's state to stderr. */
|
||||
extern void __analyzer_dump_region_model (void);
|
||||
|
||||
/* Emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the
|
||||
truthfulness of the argument. */
|
||||
extern void __analyzer_eval (int);
|
||||
|
||||
#endif /* #ifndef ANALYZER_DECLS_H. */
|
163
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
Normal file
163
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-0.c
Normal file
|
@ -0,0 +1,163 @@
|
|||
/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=0" } */
|
||||
/* { dg-enable-nn-line-numbers "" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void calls_free_1 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_1 (void *ptr, int a, int b)
|
||||
{
|
||||
if (a)
|
||||
calls_free_1 (ptr);
|
||||
|
||||
if (b)
|
||||
{
|
||||
}
|
||||
else
|
||||
calls_free_1 (ptr);
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_1': event 1
|
||||
|
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (1) calling 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': event 2
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (2) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_1': events 3-4
|
||||
|
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) returning to 'test_1' from 'calls_free_1'
|
||||
|......
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': event 5
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (5) second 'free' here; first 'free' was at (2)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
void calls_free_2 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_2 (void *ptr, int a, int b)
|
||||
{
|
||||
switch (a)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (b)
|
||||
{
|
||||
default:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 42:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_2': event 1
|
||||
|
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (1) calling 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': event 2
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (2) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_2': events 3-4
|
||||
|
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) returning to 'test_2' from 'calls_free_2'
|
||||
|......
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': event 5
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (5) second 'free' here; first 'free' was at (2)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
// TODO: range cases
|
||||
|
||||
/* The call/return to this function shouldn't appear in the path. */
|
||||
|
||||
void called_by_test_3 (void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_3 (void *ptr)
|
||||
{
|
||||
free (ptr);
|
||||
called_by_test_3 ();
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_3': events 1-2
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (1) first 'free' here
|
||||
| NN | called_by_test_3 ();
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) second 'free' here; first 'free' was at (1)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
191
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
Normal file
191
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-1.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=1" } */
|
||||
/* { dg-enable-nn-line-numbers "" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void calls_free_1 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_1 (void *ptr, int a, int b)
|
||||
{
|
||||
if (a)
|
||||
calls_free_1 (ptr);
|
||||
|
||||
if (b)
|
||||
{
|
||||
}
|
||||
else
|
||||
calls_free_1 (ptr);
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_1': events 1-2
|
||||
|
|
||||
| NN | void test_1 (void *ptr, int a, int b)
|
||||
| | ^~~~~~
|
||||
| | |
|
||||
| | (1) entry to 'test_1'
|
||||
|......
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) calling 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': events 3-4
|
||||
|
|
||||
| NN | void calls_free_1 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) entry to 'calls_free_1'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_1': events 5-6
|
||||
|
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) returning to 'test_1' from 'calls_free_1'
|
||||
|......
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': events 7-8
|
||||
|
|
||||
| NN | void calls_free_1 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) entry to 'calls_free_1'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (8) second 'free' here; first 'free' was at (4)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
void calls_free_2 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_2 (void *ptr, int a, int b)
|
||||
{
|
||||
switch (a)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (b)
|
||||
{
|
||||
default:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 42:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_2': events 1-2
|
||||
|
|
||||
| NN | void test_2 (void *ptr, int a, int b)
|
||||
| | ^~~~~~
|
||||
| | |
|
||||
| | (1) entry to 'test_2'
|
||||
|......
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) calling 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': events 3-4
|
||||
|
|
||||
| NN | void calls_free_2 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) entry to 'calls_free_2'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_2': events 5-6
|
||||
|
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) returning to 'test_2' from 'calls_free_2'
|
||||
|......
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': events 7-8
|
||||
|
|
||||
| NN | void calls_free_2 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) entry to 'calls_free_2'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (8) second 'free' here; first 'free' was at (4)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
/* The call/return to this function shouldn't appear in the path. */
|
||||
|
||||
void called_by_test_3 (void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_3 (void *ptr)
|
||||
{
|
||||
free (ptr);
|
||||
called_by_test_3 ();
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_3': events 1-2
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (1) first 'free' here
|
||||
| NN | called_by_test_3 ();
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) second 'free' here; first 'free' was at (1)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
222
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
Normal file
222
gcc/testsuite/gcc.dg/analyzer/analyzer-verbosity-2.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fanalyzer-verbosity=2" } */
|
||||
/* { dg-enable-nn-line-numbers "" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void calls_free_1 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_1 (void *ptr, int a, int b)
|
||||
{
|
||||
if (a)
|
||||
calls_free_1 (ptr);
|
||||
|
||||
if (b)
|
||||
{
|
||||
}
|
||||
else
|
||||
calls_free_1 (ptr);
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_1': events 1-4
|
||||
|
|
||||
| NN | void test_1 (void *ptr, int a, int b)
|
||||
| | ^~~~~~
|
||||
| | |
|
||||
| | (1) entry to 'test_1'
|
||||
| NN | {
|
||||
| NN | if (a)
|
||||
| | ~
|
||||
| | |
|
||||
| | (2) following 'true' branch (when 'a != 0')...
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) ...to here
|
||||
| | (4) calling 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': events 5-6
|
||||
|
|
||||
| NN | void calls_free_1 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) entry to 'calls_free_1'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_1': events 7-10
|
||||
|
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) returning to 'test_1' from 'calls_free_1'
|
||||
| NN |
|
||||
| NN | if (b)
|
||||
| | ~
|
||||
| | |
|
||||
| | (8) following 'false' branch (when 'b == 0')...
|
||||
|......
|
||||
| NN | calls_free_1 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (9) ...to here
|
||||
| | (10) passing freed pointer 'ptr' in call to 'calls_free_1' from 'test_1'
|
||||
|
|
||||
+--> 'calls_free_1': events 11-12
|
||||
|
|
||||
| NN | void calls_free_1 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (11) entry to 'calls_free_1'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (12) second 'free' here; first 'free' was at (6)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
void calls_free_2 (void *ptr)
|
||||
{
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
void test_2 (void *ptr, int a, int b)
|
||||
{
|
||||
switch (a)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 3:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (b)
|
||||
{
|
||||
default:
|
||||
calls_free_2 (ptr);
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 42:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_2': events 1-4
|
||||
|
|
||||
| NN | void test_2 (void *ptr, int a, int b)
|
||||
| | ^~~~~~
|
||||
| | |
|
||||
| | (1) entry to 'test_2'
|
||||
| NN | {
|
||||
| NN | switch (a)
|
||||
| | ~~~~~~
|
||||
| | |
|
||||
| | (2) following 'case 3:' branch...
|
||||
|......
|
||||
| NN | case 3:
|
||||
| | ~~~~
|
||||
| | |
|
||||
| | (3) ...to here
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) calling 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': events 5-6
|
||||
|
|
||||
| NN | void calls_free_2 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) entry to 'calls_free_2'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) first 'free' here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test_2': events 7-10
|
||||
|
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) returning to 'test_2' from 'calls_free_2'
|
||||
|......
|
||||
| NN | switch (b)
|
||||
| | ~~~~~~
|
||||
| | |
|
||||
| | (8) following 'default:' branch...
|
||||
| NN | {
|
||||
| NN | default:
|
||||
| | ~~~~~~~
|
||||
| | |
|
||||
| | (9) ...to here
|
||||
| NN | calls_free_2 (ptr);
|
||||
| | ~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (10) passing freed pointer 'ptr' in call to 'calls_free_2' from 'test_2'
|
||||
|
|
||||
+--> 'calls_free_2': events 11-12
|
||||
|
|
||||
| NN | void calls_free_2 (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (11) entry to 'calls_free_2'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (12) second 'free' here; first 'free' was at (6)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
// TODO: range cases
|
||||
|
||||
/* The call/return to this function shouldn't appear in the path. */
|
||||
|
||||
void called_by_test_3 (void)
|
||||
{
|
||||
}
|
||||
|
||||
void test_3 (void *ptr)
|
||||
{
|
||||
free (ptr);
|
||||
called_by_test_3 ();
|
||||
free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
|
||||
}
|
||||
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | free (ptr);
|
||||
| ^~~~~~~~~~
|
||||
'test_3': events 1-2
|
||||
|
|
||||
| NN | free (ptr);
|
||||
| | ^~~~~~~~~~
|
||||
| | |
|
||||
| | (1) first 'free' here
|
||||
| NN | called_by_test_3 ();
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) second 'free' here; first 'free' was at (1)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
49
gcc/testsuite/gcc.dg/analyzer/analyzer.exp
Normal file
49
gcc/testsuite/gcc.dg/analyzer/analyzer.exp
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Copyright (C) 2019 Free Software Foundation, Inc.
|
||||
|
||||
# This program 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 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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/>.
|
||||
|
||||
# GCC testsuite that uses the `dg.exp' driver.
|
||||
|
||||
# Load support procs.
|
||||
load_lib gcc-dg.exp
|
||||
|
||||
# If the analyzer has not been enabled, bail.
|
||||
if { ![check_effective_target_analyzer] } {
|
||||
return
|
||||
}
|
||||
|
||||
global DEFAULT_CFLAGS
|
||||
if [info exists DEFAULT_CFLAGS] then {
|
||||
set save_default_cflags $DEFAULT_CFLAGS
|
||||
}
|
||||
|
||||
# If a testcase doesn't have special options, use these.
|
||||
set DEFAULT_CFLAGS "-fanalyzer -fdiagnostics-path-format=separate-events -Wanalyzer-too-complex -fanalyzer-call-summaries"
|
||||
|
||||
# Initialize `dg'.
|
||||
dg-init
|
||||
|
||||
# Main loop.
|
||||
dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\]]] \
|
||||
"" $DEFAULT_CFLAGS
|
||||
|
||||
# All done.
|
||||
dg-finish
|
||||
|
||||
if [info exists save_default_cflags] {
|
||||
set DEFAULT_CFLAGS $save_default_cflags
|
||||
} else {
|
||||
unset DEFAULT_CFLAGS
|
||||
}
|
81
gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
Normal file
81
gcc/testsuite/gcc.dg/analyzer/attribute-nonnull.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
extern void foo(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'foo' must be non-null" } */
|
||||
__attribute__((nonnull (1, 3)));
|
||||
|
||||
extern void bar(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1 of 'bar' must be non-null" } */
|
||||
__attribute__((nonnull));
|
||||
|
||||
// TODO: complain about NULL and possible NULL args
|
||||
// FIXME: ought to complain about NULL args
|
||||
|
||||
void test_1 (void *p, void *q, void *r)
|
||||
{
|
||||
foo(p, q, r);
|
||||
foo(NULL, q, r);
|
||||
foo(p, NULL, r);
|
||||
foo(p, q, NULL);
|
||||
}
|
||||
|
||||
void test_1a (void *q, void *r)
|
||||
{
|
||||
void *p = NULL;
|
||||
foo(p, q, r); /* { dg-warning "use of NULL 'p' where non-null expected" } */
|
||||
/* { dg-message "argument 1 \\('p'\\) NULL where non-null expected" "" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_2 (void *p, void *q, void *r)
|
||||
{
|
||||
bar(p, q, r);
|
||||
bar(NULL, q, r);
|
||||
bar(p, NULL, r);
|
||||
bar(p, q, NULL);
|
||||
}
|
||||
|
||||
void test_3 (void *q, void *r)
|
||||
{
|
||||
void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return NULL" } */
|
||||
|
||||
foo(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
|
||||
/* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
|
||||
|
||||
foo(p, q, r);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
void test_4 (void *q, void *r)
|
||||
{
|
||||
void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return NULL" } */
|
||||
|
||||
bar(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
|
||||
/* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
|
||||
|
||||
bar(p, q, r);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* Verify that we detect passing NULL to a __attribute__((nonnull)) function
|
||||
when it's called via a function pointer. */
|
||||
|
||||
typedef void (*bar_t)(void *ptrA, void *ptrB, void *ptrC);
|
||||
|
||||
static bar_t __attribute__((noinline))
|
||||
get_bar (void)
|
||||
{
|
||||
return bar;
|
||||
}
|
||||
|
||||
void test_5 (void *q, void *r)
|
||||
{
|
||||
void *p = malloc(1024); /* { dg-message "\\(1\\) this call could return NULL" } */
|
||||
bar_t cb = get_bar ();
|
||||
cb(p, q, r); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
|
||||
/* { dg-message "argument 1 \\('p'\\) from \\(1\\) could be NULL where non-null expected" "" { target *-*-* } .-1 } */
|
||||
/* TODO: do we want an event showing where cb is assigned "bar"? */
|
||||
|
||||
cb(p, q, r);
|
||||
|
||||
free(p);
|
||||
}
|
14
gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
Normal file
14
gcc/testsuite/gcc.dg/analyzer/call-summaries-1.c
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* { dg-additional-options "-fanalyzer-call-summaries" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void calls_free (void *p)
|
||||
{
|
||||
free (p); /* { dg-warning "double-'free' of 'p'" } */
|
||||
}
|
||||
|
||||
void test (void *q)
|
||||
{
|
||||
calls_free (q);
|
||||
calls_free (q);
|
||||
}
|
45
gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
Normal file
45
gcc/testsuite/gcc.dg/analyzer/conditionals-2.c
Normal file
|
@ -0,0 +1,45 @@
|
|||
// TODO: run this test case at every optimization level
|
||||
/* { dg-additional-options "-O2" } */
|
||||
|
||||
#include <stddef.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
#define Z_NULL 0
|
||||
|
||||
static void __attribute__((noinline))
|
||||
test_1_callee (void *p, void *q)
|
||||
{
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
|
||||
|
||||
__analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_1 (void *p, void *q)
|
||||
{
|
||||
if (p == Z_NULL || q == Z_NULL)
|
||||
return;
|
||||
|
||||
test_1_callee (p, q);
|
||||
}
|
||||
|
||||
static void __attribute__((noinline))
|
||||
test_2_callee (void *p, void *q)
|
||||
{
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 exploded node" } */
|
||||
|
||||
__analyzer_eval (p == Z_NULL); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (p != Z_NULL); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (q == Z_NULL); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (q != Z_NULL); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_2 (void *p, void *q)
|
||||
{
|
||||
if (p != Z_NULL && q != Z_NULL)
|
||||
test_2_callee (p, q);
|
||||
}
|
47
gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
Normal file
47
gcc/testsuite/gcc.dg/analyzer/conditionals-3.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* { dg-additional-options "-fno-analyzer-state-merge" } */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
static void only_called_when_flag_a_true (int i)
|
||||
{
|
||||
__analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
static void only_called_when_flag_b_true (int i)
|
||||
{
|
||||
__analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
int test_1 (int flag_a, int flag_b)
|
||||
{
|
||||
int i = 17;
|
||||
|
||||
__analyzer_eval (flag_a); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
if (flag_a)
|
||||
{
|
||||
__analyzer_eval (flag_a); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
|
||||
i = 42;
|
||||
}
|
||||
|
||||
__analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
if (flag_a)
|
||||
{
|
||||
__analyzer_eval (flag_a); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == 17); /* { dg-warning "FALSE" } */
|
||||
only_called_when_flag_a_true (i);
|
||||
}
|
||||
else
|
||||
{
|
||||
__analyzer_eval (flag_a); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (flag_b); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 42); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
|
||||
only_called_when_flag_b_true (i);
|
||||
}
|
||||
}
|
159
gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
Normal file
159
gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
Normal file
|
@ -0,0 +1,159 @@
|
|||
/* { dg-additional-options "-fno-analyzer-transitivity" } */
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
void test (int i, int j)
|
||||
{
|
||||
if (i > 4)
|
||||
{
|
||||
__analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i < 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
/* Tests of transitivity. */
|
||||
if (j < i)
|
||||
{
|
||||
__analyzer_eval (j < i); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (j <= 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
else
|
||||
{
|
||||
__analyzer_eval (j >= i); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (j > 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "FALSE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
}
|
||||
|
||||
void test_2 (int i, int j, int k)
|
||||
{
|
||||
if (i >= j)
|
||||
{
|
||||
__analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
|
||||
if (j >= k)
|
||||
{
|
||||
__analyzer_eval (i >= k); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
|
||||
if (k >= i)
|
||||
__analyzer_eval (i == k); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_3 (int flag, unsigned int i)
|
||||
{
|
||||
if (!flag) {
|
||||
return;
|
||||
}
|
||||
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
|
||||
if (i>0) {
|
||||
__analyzer_eval (i > 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
} else {
|
||||
__analyzer_eval (i <= 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_int_gt_lt (int i)
|
||||
{
|
||||
if (i > 3)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_lt (float f)
|
||||
{
|
||||
if (f > 3)
|
||||
if (f < 5)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_ge_lt (int i)
|
||||
{
|
||||
if (i >= 4)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_lt (float f)
|
||||
{
|
||||
if (f >= 4)
|
||||
if (f < 5)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_gt_le (int i)
|
||||
{
|
||||
if (i > 3)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_le (float f)
|
||||
{
|
||||
if (f > 3)
|
||||
if (f <= 4)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_ge_le (int i)
|
||||
{
|
||||
if (i >= 4)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_le (float f)
|
||||
{
|
||||
if (f >= 4)
|
||||
if (f <= 4)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "" { xfail *-*-* } .-1 } */
|
||||
}
|
144
gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
Normal file
144
gcc/testsuite/gcc.dg/analyzer/conditionals-trans.c
Normal file
|
@ -0,0 +1,144 @@
|
|||
/* { dg-additional-options "-fanalyzer-transitivity" } */
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
void test (int i, int j)
|
||||
{
|
||||
if (i > 4)
|
||||
{
|
||||
__analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i < 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
/* Tests of transitivity. */
|
||||
if (j < i)
|
||||
{
|
||||
__analyzer_eval (j < i); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (j <= 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
else
|
||||
{
|
||||
__analyzer_eval (j >= i); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (j > 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__analyzer_eval (i > 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
}
|
||||
|
||||
void test_2 (int i, int j, int k)
|
||||
{
|
||||
if (i >= j)
|
||||
{
|
||||
__analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
|
||||
if (j >= k)
|
||||
{
|
||||
__analyzer_eval (i >= k); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
|
||||
if (k >= i)
|
||||
__analyzer_eval (i == k); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_3 (int flag, unsigned int i)
|
||||
{
|
||||
if (!flag) {
|
||||
return;
|
||||
}
|
||||
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
|
||||
if (i>0) {
|
||||
__analyzer_eval (i > 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
} else {
|
||||
__analyzer_eval (i <= 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (flag); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_int_gt_lt (int i)
|
||||
{
|
||||
if (i > 3)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_lt (float f)
|
||||
{
|
||||
if (f > 3)
|
||||
if (f < 5)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_ge_lt (int i)
|
||||
{
|
||||
if (i >= 4)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_lt (float f)
|
||||
{
|
||||
if (f >= 4)
|
||||
if (f < 5)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_gt_le (int i)
|
||||
{
|
||||
if (i > 3)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_le (float f)
|
||||
{
|
||||
if (f > 3)
|
||||
if (f <= 4)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_range_int_ge_le (int i)
|
||||
{
|
||||
if (i >= 4)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_le (float f)
|
||||
{
|
||||
if (f >= 4)
|
||||
if (f <= 4)
|
||||
__analyzer_eval (f == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
1085
gcc/testsuite/gcc.dg/analyzer/data-model-1.c
Normal file
1085
gcc/testsuite/gcc.dg/analyzer/data-model-1.c
Normal file
File diff suppressed because it is too large
Load diff
17
gcc/testsuite/gcc.dg/analyzer/data-model-10.c
Normal file
17
gcc/testsuite/gcc.dg/analyzer/data-model-10.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
struct foo
|
||||
{
|
||||
char **m_f;
|
||||
};
|
||||
|
||||
struct foo *
|
||||
test (void)
|
||||
{
|
||||
struct foo *new_table = (struct foo *) malloc(sizeof(struct foo));
|
||||
if (!new_table)
|
||||
return NULL;
|
||||
new_table->m_f = (char **)malloc(sizeof(char **));
|
||||
*new_table->m_f = NULL; /* { dg-warning "dereference of possibly-NULL '<unknown>'" } */ // FIXME: something better than "unknown" here
|
||||
return new_table;
|
||||
}
|
6
gcc/testsuite/gcc.dg/analyzer/data-model-11.c
Normal file
6
gcc/testsuite/gcc.dg/analyzer/data-model-11.c
Normal file
|
@ -0,0 +1,6 @@
|
|||
int test (void)
|
||||
{
|
||||
unsigned char *s = "abc";
|
||||
char *t = "xyz";
|
||||
return s[1] + t[1];
|
||||
}
|
13
gcc/testsuite/gcc.dg/analyzer/data-model-12.c
Normal file
13
gcc/testsuite/gcc.dg/analyzer/data-model-12.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* Mismatching decl of foo. */
|
||||
|
||||
int foo ();
|
||||
|
||||
int bar (void)
|
||||
{
|
||||
return foo() + 1;
|
||||
}
|
||||
|
||||
int foo (int x, int y)
|
||||
{
|
||||
return x * y;
|
||||
}
|
21
gcc/testsuite/gcc.dg/analyzer/data-model-13.c
Normal file
21
gcc/testsuite/gcc.dg/analyzer/data-model-13.c
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
union
|
||||
{
|
||||
void *ptr_val;
|
||||
int int_val;
|
||||
} global_union;
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
global_union.ptr_val = malloc (1024);
|
||||
}
|
||||
|
||||
void test_2 (void)
|
||||
{
|
||||
global_union.ptr_val = malloc (1024); /* { dg-message "allocated here" } */
|
||||
global_union.int_val = 0;
|
||||
} /* { dg-warning "leak of '<unknown>' " } */
|
||||
/* TODO: something better than "<unknown>". */
|
||||
/* TODO: better location for the leak. */
|
||||
|
24
gcc/testsuite/gcc.dg/analyzer/data-model-14.c
Normal file
24
gcc/testsuite/gcc.dg/analyzer/data-model-14.c
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* FIXME: we shouldn't need this. */
|
||||
/* { dg-additional-options "-fanalyzer-fine-grained" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void *global_ptr;
|
||||
|
||||
void test_1 (int i)
|
||||
{
|
||||
global_ptr = malloc (1024); /* { dg-message "allocated here" } */
|
||||
*(int *)&global_ptr = i; /* { dg-warning "leak of '<unknown>'" } */
|
||||
// TODO: something better than "<unknown>" here ^^^
|
||||
}
|
||||
|
||||
void test_2 (int i)
|
||||
{
|
||||
void *p = malloc (1024); /* { dg-message "allocated here" "" { xfail *-*-* } } */
|
||||
// TODO(xfail)
|
||||
global_ptr = p;
|
||||
*(int *)&p = i;
|
||||
p = global_ptr;
|
||||
free (p);
|
||||
free (global_ptr); /* { dg-warning "double-'free' of 'p'" } */
|
||||
}
|
34
gcc/testsuite/gcc.dg/analyzer/data-model-15.c
Normal file
34
gcc/testsuite/gcc.dg/analyzer/data-model-15.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <string.h>
|
||||
|
||||
struct coord
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
};
|
||||
|
||||
struct tri {
|
||||
struct coord verts[3];
|
||||
};
|
||||
|
||||
double test_1 (void)
|
||||
{
|
||||
struct tri t;
|
||||
memset (&t, 0, sizeof (struct tri));
|
||||
return t.verts[1].y;
|
||||
}
|
||||
|
||||
int test_2 (const struct coord *c1, const struct coord *c2, double r_squared)
|
||||
{
|
||||
double dx = c1->x - c2->x;
|
||||
double dy = c1->y - c2->y;
|
||||
double dz = c1->z - c2->z;
|
||||
return (dx * dx) + (dy * dy) + (dz * dz) <= r_squared;
|
||||
}
|
||||
|
||||
int test_3 (const struct coord *c1, const struct coord *c2, struct coord *out)
|
||||
{
|
||||
out->x = c1->x + c2->x;
|
||||
out->y = c1->y + c2->y;
|
||||
out->z = c1->z + c2->z;
|
||||
}
|
52
gcc/testsuite/gcc.dg/analyzer/data-model-16.c
Normal file
52
gcc/testsuite/gcc.dg/analyzer/data-model-16.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* Labels as values. */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
extern void foo (void);
|
||||
|
||||
void *x, *y, *z;
|
||||
|
||||
void test (void)
|
||||
{
|
||||
label0:
|
||||
foo ();
|
||||
label1:
|
||||
foo ();
|
||||
label2:
|
||||
foo ();
|
||||
|
||||
x = &&label0;
|
||||
y = &&label1;
|
||||
z = &&label2;
|
||||
|
||||
__analyzer_eval (x == x); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x == y); /* { dg-warning "FALSE" } */
|
||||
}
|
||||
|
||||
void test_2 (int i)
|
||||
{
|
||||
static void *array[] = { &&label0, &&label1, &&label2 };
|
||||
goto *array[i];
|
||||
|
||||
label0:
|
||||
foo ();
|
||||
label1:
|
||||
foo ();
|
||||
label2:
|
||||
foo ();
|
||||
}
|
||||
|
||||
void test_3 (int i)
|
||||
{
|
||||
static const int array[] = { &&label0 - &&label0,
|
||||
&&label1 - &&label0,
|
||||
&&label2 - &&label0 };
|
||||
goto *(&&label0 + array[i]);
|
||||
|
||||
label0:
|
||||
foo ();
|
||||
label1:
|
||||
foo ();
|
||||
label2:
|
||||
foo ();
|
||||
}
|
20
gcc/testsuite/gcc.dg/analyzer/data-model-17.c
Normal file
20
gcc/testsuite/gcc.dg/analyzer/data-model-17.c
Normal file
|
@ -0,0 +1,20 @@
|
|||
typedef struct foo {} foo_t;
|
||||
|
||||
typedef void (*func_t)(foo_t *s);
|
||||
|
||||
void cb_1 (foo_t *s);
|
||||
void cb_2 (foo_t *s);
|
||||
|
||||
typedef struct config_s {
|
||||
func_t func;
|
||||
} config;
|
||||
|
||||
static const config table[2] = {
|
||||
{ cb_1 },
|
||||
{ cb_2 }
|
||||
};
|
||||
|
||||
int deflate (foo_t *s, int which)
|
||||
{
|
||||
(*(table[which].func))(s);
|
||||
}
|
22
gcc/testsuite/gcc.dg/analyzer/data-model-18.c
Normal file
22
gcc/testsuite/gcc.dg/analyzer/data-model-18.c
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test (int *p, int i, int j)
|
||||
{
|
||||
p[3] = 42;
|
||||
__analyzer_eval (p[3] == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (*(p + 3) == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p[i] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[j] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
//__analyzer_dump ();
|
||||
|
||||
p[i] = 17;
|
||||
|
||||
//__analyzer_dump ();
|
||||
|
||||
__analyzer_eval (p[3] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[i] == 17); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p[j] == 17); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
|
||||
/* { dg-bogus "TRUE" "" { xfail *-*-* } .-1 } */
|
||||
// FIXME(xfails) ^^^
|
||||
}
|
31
gcc/testsuite/gcc.dg/analyzer/data-model-19.c
Normal file
31
gcc/testsuite/gcc.dg/analyzer/data-model-19.c
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* { dg-additional-options "-fgimple" } */
|
||||
|
||||
typedef long long int i64;
|
||||
|
||||
int __GIMPLE (ssa)
|
||||
test (i64 * pA, i64 iB)
|
||||
{
|
||||
__complex__ long long int D_37702;
|
||||
int D_37701;
|
||||
long long int _1;
|
||||
long long int _2;
|
||||
long long int _3;
|
||||
_Bool _4;
|
||||
__complex__ long long int _8;
|
||||
int _10;
|
||||
|
||||
__BB(2):
|
||||
_1 = __MEM <i64> (pA_6(D));
|
||||
_8 = .ADD_OVERFLOW (_1, iB_7(D));
|
||||
_2 = __real _8;
|
||||
__MEM <i64> (pA_6(D)) = _2;
|
||||
_3 = __imag _8;
|
||||
_4 = (_Bool) _3;
|
||||
_10 = (int) _4;
|
||||
goto __BB3;
|
||||
|
||||
__BB(3):
|
||||
L0:
|
||||
return _10;
|
||||
|
||||
}
|
13
gcc/testsuite/gcc.dg/analyzer/data-model-2.c
Normal file
13
gcc/testsuite/gcc.dg/analyzer/data-model-2.c
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* { dg-additional-options "-O2" } */
|
||||
/* TODO:is there a way to automatically run the tests on various
|
||||
optimizations levels, and with/without debuginfo, rather than
|
||||
hardcoding options? Adapt from torture .exp, presumably. */
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int test_1 (void)
|
||||
{
|
||||
return 0;
|
||||
}
|
15
gcc/testsuite/gcc.dg/analyzer/data-model-3.c
Normal file
15
gcc/testsuite/gcc.dg/analyzer/data-model-3.c
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* { dg-additional-options "-O2" } */
|
||||
/* TODO:is there a way to automatically run the tests on various
|
||||
optimizations levels, and with/without debuginfo, rather than
|
||||
hardcoding options? Adapt from torture .exp, presumably. */
|
||||
|
||||
#include <stdio.h>
|
||||
int
|
||||
main ()
|
||||
{
|
||||
FILE *f = fopen ("conftest.out", "w");
|
||||
return ferror (f) || fclose (f) != 0;
|
||||
|
||||
;
|
||||
return 0;
|
||||
}
|
16
gcc/testsuite/gcc.dg/analyzer/data-model-4.c
Normal file
16
gcc/testsuite/gcc.dg/analyzer/data-model-4.c
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* { dg-additional-options "-fexceptions" } */
|
||||
/* TODO:is there a way to automatically run the tests on various
|
||||
optimizations levels, and with/without debuginfo, rather than
|
||||
hardcoding options? Adapt from torture .exp, presumably. */
|
||||
|
||||
#include <stdio.h>
|
||||
int
|
||||
main ()
|
||||
{
|
||||
FILE *f = fopen ("conftest.out", "w");
|
||||
return ferror (f) || fclose (f) != 0;
|
||||
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
|
100
gcc/testsuite/gcc.dg/analyzer/data-model-5.c
Normal file
100
gcc/testsuite/gcc.dg/analyzer/data-model-5.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/* A toy re-implementation of CPython's object model. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct base_obj
|
||||
{
|
||||
struct type_obj *ob_type;
|
||||
int ob_refcnt;
|
||||
} base_obj;
|
||||
|
||||
typedef struct type_obj
|
||||
{
|
||||
base_obj tp_base;
|
||||
void (*tp_dealloc) (base_obj *);
|
||||
} type_obj;
|
||||
|
||||
typedef struct tuple_obj
|
||||
{
|
||||
base_obj tup_base;
|
||||
int num_elements;
|
||||
base_obj elements[];
|
||||
} tuple_obj;
|
||||
|
||||
typedef struct list_obj
|
||||
{
|
||||
base_obj list_base;
|
||||
int num_elements;
|
||||
base_obj *elements;
|
||||
} list_obj;
|
||||
|
||||
typedef struct string_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
} string_obj;
|
||||
|
||||
extern void type_del (base_obj *);
|
||||
extern void tuple_del (base_obj *);
|
||||
extern void str_del (base_obj *);
|
||||
|
||||
type_obj type_type = {
|
||||
{ &type_type, 1},
|
||||
type_del
|
||||
};
|
||||
|
||||
type_obj tuple_type = {
|
||||
{ &type_type, 1},
|
||||
tuple_del
|
||||
};
|
||||
|
||||
type_obj str_type = {
|
||||
{ &str_type, 1},
|
||||
str_del
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
||||
{
|
||||
base_obj *obj = (base_obj *)malloc (sz);
|
||||
if (!obj)
|
||||
return NULL;
|
||||
obj->ob_type = ob_type;
|
||||
obj->ob_refcnt = 1;
|
||||
return obj;
|
||||
}
|
||||
|
||||
base_obj *new_string_obj (const char *str)
|
||||
{
|
||||
//__analyzer_dump ();
|
||||
size_t len = strlen (str);
|
||||
#if 1
|
||||
string_obj *str_obj
|
||||
= (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
|
||||
#else
|
||||
string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
|
||||
if (!str_obj)
|
||||
return NULL;
|
||||
str_obj->str_base.ob_type = &str_type;
|
||||
str_obj->str_base.ob_refcnt = 1;
|
||||
#endif
|
||||
str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
|
||||
memcpy (str_obj->str_buf, str, len);
|
||||
str_obj->str_buf[len] = '\0';
|
||||
return (base_obj *)str_obj;
|
||||
}
|
||||
|
||||
void unref (base_obj *obj)
|
||||
{
|
||||
if (--obj->ob_refcnt == 0) /* { dg-bogus "dereference of uninitialized pointer 'obj'" } */
|
||||
obj->ob_type->tp_dealloc (obj);
|
||||
}
|
||||
|
||||
void test_1 (const char *str)
|
||||
{
|
||||
base_obj *obj = new_string_obj (str);
|
||||
//__analyzer_dump();
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak" } */
|
91
gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
Normal file
91
gcc/testsuite/gcc.dg/analyzer/data-model-5b.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
/* A toy re-implementation of CPython's object model. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct base_obj base_obj;
|
||||
typedef struct type_obj type_obj;
|
||||
typedef struct string_obj string_obj;
|
||||
|
||||
struct base_obj
|
||||
{
|
||||
struct type_obj *ob_type;
|
||||
int ob_refcnt;
|
||||
};
|
||||
|
||||
struct type_obj
|
||||
{
|
||||
base_obj tp_base;
|
||||
void (*tp_dealloc) (base_obj *);
|
||||
};
|
||||
|
||||
struct string_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
};
|
||||
|
||||
extern void type_del (base_obj *);
|
||||
extern void str_del (base_obj *);
|
||||
|
||||
type_obj type_type = {
|
||||
{ &type_type, 1},
|
||||
type_del
|
||||
};
|
||||
|
||||
type_obj str_type = {
|
||||
{ &str_type, 1},
|
||||
str_del
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
||||
{
|
||||
base_obj *obj = (base_obj *)malloc (sz);
|
||||
if (!obj)
|
||||
return NULL;
|
||||
obj->ob_type = ob_type;
|
||||
obj->ob_refcnt = 1;
|
||||
return obj;
|
||||
}
|
||||
|
||||
string_obj *new_string_obj (const char *str)
|
||||
{
|
||||
//__analyzer_dump ();
|
||||
size_t len = strlen (str);
|
||||
#if 1
|
||||
string_obj *str_obj
|
||||
= (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
|
||||
#else
|
||||
string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
|
||||
if (!str_obj)
|
||||
return NULL;
|
||||
str_obj->str_base.ob_type = &str_type;
|
||||
str_obj->str_base.ob_refcnt = 1;
|
||||
#endif
|
||||
str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
|
||||
memcpy (str_obj->str_buf, str, len);
|
||||
str_obj->str_buf[len] = '\0';
|
||||
return str_obj;
|
||||
}
|
||||
|
||||
void unref (string_obj *obj)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
if (--obj->str_base.ob_refcnt == 0)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
obj->str_base.ob_type->tp_dealloc ((base_obj *)obj); /* { dg-bogus "use of uninitialized value '<unknown>'" "" { xfail *-*-* } } */
|
||||
// TODO (xfail): not sure what's going on here
|
||||
}
|
||||
}
|
||||
|
||||
void test_1 (const char *str)
|
||||
{
|
||||
string_obj *obj = new_string_obj (str);
|
||||
//__analyzer_dump();
|
||||
if (obj)
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
|
||||
// TODO (xfail): not sure why this is treated as leaking
|
84
gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
Normal file
84
gcc/testsuite/gcc.dg/analyzer/data-model-5c.c
Normal file
|
@ -0,0 +1,84 @@
|
|||
/* A toy re-implementation of CPython's object model. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct base_obj base_obj;
|
||||
typedef struct type_obj type_obj;
|
||||
typedef struct string_obj string_obj;
|
||||
|
||||
struct base_obj
|
||||
{
|
||||
struct type_obj *ob_type;
|
||||
int ob_refcnt;
|
||||
};
|
||||
|
||||
struct type_obj
|
||||
{
|
||||
base_obj tp_base;
|
||||
};
|
||||
|
||||
struct string_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
};
|
||||
|
||||
type_obj type_type = {
|
||||
{ &type_type, 1},
|
||||
};
|
||||
|
||||
type_obj str_type = {
|
||||
{ &str_type, 1},
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
||||
{
|
||||
base_obj *obj = (base_obj *)malloc (sz);
|
||||
if (!obj)
|
||||
return NULL;
|
||||
obj->ob_type = ob_type;
|
||||
obj->ob_refcnt = 1;
|
||||
return obj;
|
||||
}
|
||||
|
||||
string_obj *new_string_obj (const char *str)
|
||||
{
|
||||
//__analyzer_dump ();
|
||||
size_t len = strlen (str);
|
||||
#if 1
|
||||
string_obj *str_obj
|
||||
= (string_obj *)alloc_obj (&str_type, sizeof (string_obj) + len + 1);
|
||||
#else
|
||||
string_obj *str_obj = (string_obj *)malloc (sizeof (string_obj) + len + 1);
|
||||
if (!str_obj)
|
||||
return NULL;
|
||||
str_obj->str_base.ob_type = &str_type;
|
||||
str_obj->str_base.ob_refcnt = 1;
|
||||
#endif
|
||||
str_obj->str_len = len; /* { dg-warning "dereference of NULL 'str_obj'" } */
|
||||
memcpy (str_obj->str_buf, str, len);
|
||||
str_obj->str_buf[len] = '\0';
|
||||
return str_obj;
|
||||
}
|
||||
|
||||
void unref (string_obj *obj)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
if (--obj->str_base.ob_refcnt == 0)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
free (obj);
|
||||
}
|
||||
}
|
||||
|
||||
void test_1 (const char *str)
|
||||
{
|
||||
string_obj *obj = new_string_obj (str);
|
||||
//__analyzer_dump();
|
||||
if (obj)
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
|
||||
// TODO (xfail): not sure why this is treated as leaking
|
64
gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
Normal file
64
gcc/testsuite/gcc.dg/analyzer/data-model-5d.c
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* A toy re-implementation of CPython's object model. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
typedef struct base_obj base_obj;
|
||||
typedef struct type_obj type_obj;
|
||||
typedef struct string_obj string_obj;
|
||||
|
||||
struct base_obj
|
||||
{
|
||||
struct type_obj *ob_type;
|
||||
int ob_refcnt;
|
||||
};
|
||||
|
||||
struct type_obj
|
||||
{
|
||||
base_obj tp_base;
|
||||
};
|
||||
|
||||
struct string_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
};
|
||||
|
||||
type_obj type_type = {
|
||||
{ &type_type, 1},
|
||||
};
|
||||
|
||||
type_obj str_type = {
|
||||
{ &str_type, 1},
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
||||
{
|
||||
base_obj *obj = (base_obj *)malloc (sz);
|
||||
if (!obj)
|
||||
return NULL;
|
||||
obj->ob_type = ob_type;
|
||||
obj->ob_refcnt = 1;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void unref (base_obj *obj)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
if (--obj->ob_refcnt == 0)
|
||||
free (obj);
|
||||
}
|
||||
|
||||
void test_1 ()
|
||||
{
|
||||
base_obj *obj = alloc_obj (&str_type, sizeof (string_obj));
|
||||
if (obj)
|
||||
{
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
|
||||
unref (obj);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
}
|
||||
}
|
14
gcc/testsuite/gcc.dg/analyzer/data-model-6.c
Normal file
14
gcc/testsuite/gcc.dg/analyzer/data-model-6.c
Normal file
|
@ -0,0 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
/* Verify that we don't accumulate state after a malloc/free pair. */
|
||||
|
||||
void test (void)
|
||||
{
|
||||
void *ptr;
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
ptr = malloc (1024);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
|
||||
free (ptr);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
}
|
20
gcc/testsuite/gcc.dg/analyzer/data-model-7.c
Normal file
20
gcc/testsuite/gcc.dg/analyzer/data-model-7.c
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* { dg-additional-options "-fno-analyzer-state-merge" } */
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
int test_40 (int flag)
|
||||
{
|
||||
int i;
|
||||
if (flag)
|
||||
i = 43;
|
||||
else
|
||||
i = 17;
|
||||
|
||||
/* Without state-merging, we retain the relationship between 'flag' and 'i'. */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 exploded nodes" } */
|
||||
|
||||
if (flag)
|
||||
__analyzer_eval (i == 43); /* { dg-warning "TRUE" } */
|
||||
else
|
||||
__analyzer_eval (i == 17); /* { dg-warning "TRUE" } */
|
||||
}
|
26
gcc/testsuite/gcc.dg/analyzer/data-model-8.c
Normal file
26
gcc/testsuite/gcc.dg/analyzer/data-model-8.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
struct base
|
||||
{
|
||||
int i;
|
||||
};
|
||||
|
||||
struct sub
|
||||
{
|
||||
struct base b;
|
||||
int j;
|
||||
};
|
||||
|
||||
void test (void)
|
||||
{
|
||||
struct sub s;
|
||||
s.b.i = 3;
|
||||
s.j = 4;
|
||||
__analyzer_eval (s.b.i == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (s.j == 4); /* { dg-warning "TRUE" } */
|
||||
|
||||
struct base *bp = (struct base *)&s;
|
||||
|
||||
__analyzer_eval (bp->i == 3); /* { dg-warning "TRUE" "" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "" { target *-*-* } .-1 } */
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue