Commit 757bf1df by David Malcolm

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.
parent 08c8c973
2020-01-14 David Malcolm <dmalcolm@redhat.com>
* Makefile.in (lang_opt_files): Add analyzer.opt.
(ANALYZER_OBJS): New.
(OBJS): Add digraph.o, graphviz.o, ordered-hash-map-tests.o,
tristate.o and ANALYZER_OBJS.
(TEXI_GCCINT_FILES): Add analyzer.texi.
* common.opt (-fanalyzer): New driver option.
* config.in: Regenerate.
* configure: Regenerate.
* configure.ac (--disable-analyzer, ENABLE_ANALYZER): New option.
(gccdepdir): Also create depdir for "analyzer" subdir.
* digraph.cc: New file.
* digraph.h: New file.
* doc/analyzer.texi: New file.
* doc/gccint.texi ("Static Analyzer") New menu item.
(analyzer.texi): Include it.
* doc/invoke.texi ("Static Analyzer Options"): New list and new section.
("Warning Options"): Add static analysis warnings to the list.
(-Wno-analyzer-double-fclose): New option.
(-Wno-analyzer-double-free): New option.
(-Wno-analyzer-exposure-through-output-file): New option.
(-Wno-analyzer-file-leak): New option.
(-Wno-analyzer-free-of-non-heap): New option.
(-Wno-analyzer-malloc-leak): New option.
(-Wno-analyzer-possible-null-argument): New option.
(-Wno-analyzer-possible-null-dereference): New option.
(-Wno-analyzer-null-argument): New option.
(-Wno-analyzer-null-dereference): New option.
(-Wno-analyzer-stale-setjmp-buffer): New option.
(-Wno-analyzer-tainted-array-index): New option.
(-Wno-analyzer-use-after-free): New option.
(-Wno-analyzer-use-of-pointer-in-stale-stack-frame): New option.
(-Wno-analyzer-use-of-uninitialized-value): New option.
(-Wanalyzer-too-complex): New option.
(-fanalyzer-call-summaries): New warning.
(-fanalyzer-checker=): New warning.
(-fanalyzer-fine-grained): New warning.
(-fno-analyzer-state-merge): New warning.
(-fno-analyzer-state-purge): New warning.
(-fanalyzer-transitivity): New warning.
(-fanalyzer-verbose-edges): New warning.
(-fanalyzer-verbose-state-changes): New warning.
(-fanalyzer-verbosity=): New warning.
(-fdump-analyzer): New warning.
(-fdump-analyzer-callgraph): New warning.
(-fdump-analyzer-exploded-graph): New warning.
(-fdump-analyzer-exploded-nodes): New warning.
(-fdump-analyzer-exploded-nodes-2): New warning.
(-fdump-analyzer-exploded-nodes-3): New warning.
(-fdump-analyzer-supergraph): New warning.
* doc/sourcebuild.texi (dg-require-dot): New.
(dg-check-dot): New.
* gdbinit.in (break-on-saved-diagnostic): New command.
* graphviz.cc: New file.
* graphviz.h: New file.
* ordered-hash-map-tests.cc: New file.
* ordered-hash-map.h: New file.
* passes.def (pass_analyzer): Add before
pass_ipa_whole_program_visibility.
* selftest-run-tests.c (selftest::run_tests): Call
selftest::ordered_hash_map_tests_cc_tests.
* selftest.h (selftest::ordered_hash_map_tests_cc_tests): New
decl.
* shortest-paths.h: New file.
* timevar.def (TV_ANALYZER): New timevar.
(TV_ANALYZER_SUPERGRAPH): Likewise.
(TV_ANALYZER_STATE_PURGE): Likewise.
(TV_ANALYZER_PLAN): Likewise.
(TV_ANALYZER_SCC): Likewise.
(TV_ANALYZER_WORKLIST): Likewise.
(TV_ANALYZER_DUMP): Likewise.
(TV_ANALYZER_DIAGNOSTICS): Likewise.
(TV_ANALYZER_SHORTEST_PATHS): Likewise.
* tree-pass.h (make_pass_analyzer): New decl.
* tristate.cc: New file.
* tristate.h: New file.
2020-01-14 Uroš Bizjak <ubizjak@gmail.com>
PR target/93254
......
......@@ -567,7 +567,7 @@ xm_include_list=@xm_include_list@
xm_defines=@xm_defines@
lang_checks=
lang_checks_parallelized=
lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt
lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
lang_specs_files=@lang_specs_files@
lang_tree_files=@lang_tree_files@
target_cpu_default=@target_cpu_default@
......@@ -1214,6 +1214,32 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
c-family/c-ubsan.o c-family/known-headers.o \
c-family/c-attribs.o c-family/c-warn.o c-family/c-spellcheck.o
# Analyzer object files
ANALYZER_OBJS = \
analyzer/analysis-plan.o \
analyzer/analyzer.o \
analyzer/analyzer-logging.o \
analyzer/analyzer-pass.o \
analyzer/analyzer-selftests.o \
analyzer/call-string.o \
analyzer/checker-path.o \
analyzer/constraint-manager.o \
analyzer/diagnostic-manager.o \
analyzer/engine.o \
analyzer/pending-diagnostic.o \
analyzer/program-point.o \
analyzer/program-state.o \
analyzer/region-model.o \
analyzer/sm.o \
analyzer/sm-file.o \
analyzer/sm-malloc.o \
analyzer/sm-pattern-test.o \
analyzer/sm-sensitive.o \
analyzer/sm-signal.o \
analyzer/sm-taint.o \
analyzer/state-purge.o \
analyzer/supergraph.o
# Language-independent object files.
# We put the *-match.o and insn-*.o files first so that a parallel make
# will build them sooner, because they are large and otherwise tend to be
......@@ -1283,6 +1309,7 @@ OBJS = \
df-problems.o \
df-scan.o \
dfp.o \
digraph.o \
dojump.o \
dominance.o \
domwalk.o \
......@@ -1344,6 +1371,7 @@ OBJS = \
godump.o \
graph.o \
graphds.o \
graphviz.o \
graphite.o \
graphite-isl-ast-to-gimple.o \
graphite-dependences.o \
......@@ -1443,6 +1471,7 @@ OBJS = \
optinfo-emit-json.o \
options-save.o \
opts-global.o \
ordered-hash-map-tests.o \
passes.o \
plugin.o \
postreload-gcse.o \
......@@ -1600,6 +1629,7 @@ OBJS = \
tree-vector-builder.o \
tree-vrp.o \
tree.o \
tristate.o \
typed-splay-tree.o \
unique-ptr-tests.o \
valtrack.o \
......@@ -1617,6 +1647,7 @@ OBJS = \
wide-int-print.o \
xcoffout.o \
$(out_object_file) \
$(ANALYZER_OBJS) \
$(EXTRA_OBJS) \
$(host_hook_obj)
......@@ -3220,7 +3251,7 @@ TEXI_GCCINT_FILES = gccint.texi gcc-common.texi gcc-vers.texi \
gnu.texi gpl_v3.texi fdl.texi contrib.texi languages.texi \
sourcebuild.texi gty.texi libgcc.texi cfg.texi tree-ssa.texi \
loop.texi generic.texi gimple.texi plugins.texi optinfo.texi \
match-and-simplify.texi ux.texi poly-int.texi
match-and-simplify.texi analyzer.texi ux.texi poly-int.texi
TEXI_GCCINSTALL_FILES = install.texi install-old.texi fdl.texi \
gcc-common.texi gcc-vers.texi
......
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.
/* 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 */
/* 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 */
/* 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 */
/* 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 */
/* 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);
}
/* 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 */
/* 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 */
/* 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 */
/* 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 */
; 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.
/* 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 */
/* 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 */
/* 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 */
/* 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 */
This source diff could not be displayed because it is too large. You can view the blob instead.
/* 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 */
/* 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 */
/* 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 */
/* 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 */
/* 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 */
This source diff could not be displayed because it is too large. You can view the blob instead.
/* 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 */
/* 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')"];
}
/* 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 */
/* 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 */
/* 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 */
/* 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 */
/* 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 */
/* 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 */
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment