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:
David Malcolm 2019-09-27 09:23:16 -04:00
parent 08c8c973c0
commit 757bf1dff5
222 changed files with 40744 additions and 8 deletions

View file

@ -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

View file

@ -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
View 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.

View 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 */

View 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 */

View 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 */

View 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 */

View 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);
}

View 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 */

View 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
View 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 = &ap;
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
View 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
View 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
View 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 */

View 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 */

View 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
View 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 */

File diff suppressed because it is too large Load diff

View 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 */

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

26
gcc/analyzer/engine.h Normal file
View 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 */

View 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 */

View 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 = &ap;
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 */

View 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 */

View 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 */

View 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 */

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

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
View 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
View 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 */

View 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')"];
}

View 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 */

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 */

View file

@ -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.

View file

@ -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
View file

@ -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 ;;

View file

@ -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
View 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
View 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
View 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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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 = &ap;
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 = &ap;
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
View 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 */

View 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
View 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 */

View file

@ -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);

View file

@ -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

View file

@ -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
View 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 */

View file

@ -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.

View 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'" } */
}

View 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" } */
}

View file

@ -0,0 +1,8 @@
#include <alloca.h>
void *test (void)
{
void *ptr = alloca (64);
return ptr;
}
/* TODO: warn about escaping alloca. */

View 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 analyzers 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. */

View 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 "" } */

View 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 "" } */

View 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 "" } */

View 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
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}
}

View 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 } */
}

View 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" } */
}

File diff suppressed because it is too large Load diff

View 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;
}

View file

@ -0,0 +1,6 @@
int test (void)
{
unsigned char *s = "abc";
char *t = "xyz";
return s[1] + t[1];
}

View 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;
}

View 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. */

View 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'" } */
}

View 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;
}

View 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 ();
}

View 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);
}

View 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) ^^^
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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" } */

View 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

View 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

View 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'" } */
}
}

View 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'" } */
}

View 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" } */
}

View 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