diff --git a/.gitignore b/.gitignore index dd604e85a..4a929ce91 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,7 @@ infer/src/.project /infer/src/istd/jbuild /infer/src/scripts/jbuild /infer/src/jbuild-workspace + +# generated when looking for dead code +/infer/src/deadcode/.depend +/infer/src/deadcode/jbuild diff --git a/infer/src/Makefile b/infer/src/Makefile index cefed909d..c7e7f1a72 100644 --- a/infer/src/Makefile +++ b/infer/src/Makefile @@ -95,7 +95,7 @@ all: infer GENERATED_FROM_AUTOCONF = jbuild.common jbuild-workspace base/Version.ml -GENERATED_JBUILDS = jbuild atd/jbuild istd/jbuild scripts/jbuild +GENERATED_JBUILDS = jbuild atd/jbuild deadcode/jbuild istd/jbuild scripts/jbuild SRC_BUILD_COMMON = $(GENERATED_FROM_AUTOCONF) $(GENERATED_JBUILDS) $(OCAML_SOURCES) ifeq ($(BUILD_C_ANALYZERS),yes) @@ -159,7 +159,7 @@ roots += $(INFER_CREATE_TRACEVIEW_LINKS_MODULE) endif clusters:=base clang java IR -ml_src_files:=$(shell find . -regex '\./[a-zA-Z].*\.ml\(i\)*') +ml_src_files:=$(shell find . -not -path "./*stubs*" -regex '\./[a-zA-Z].*\.ml\(i\)*') inc_flags:=$(foreach dir,$(shell find . -type d),-I $(dir)) root_flags:=$(foreach root,$(roots),-r $(root)) cluster_flags:=$(foreach cluster,$(clusters),-c $(cluster)) @@ -265,11 +265,13 @@ $(GENERATED_JBUILDS): jbuild.common jbuild: jbuild.in atd/jbuild: atd/jbuild.in +deadcode/jbuild: deadcode/jbuild.in istd/jbuild: istd/jbuild.in scripts/jbuild: scripts/jbuild.in .PHONY: clean clean: + $(MAKE) -C deadcode clean $(REMOVE) $(INFER_TARGET) $(REMOVE) toplevel.mlpack $(REMOVE_DIR) $(BUILD_DIR) diff --git a/infer/src/deadcode/Makefile b/infer/src/deadcode/Makefile new file mode 100644 index 000000000..d5e807226 --- /dev/null +++ b/infer/src/deadcode/Makefile @@ -0,0 +1,135 @@ +# Copyright (c) 2018 - present Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the BSD style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + +# Dead code detection: type `make` in this directory. +# +# OCaml will not detect dead code as soon as it gets exported in a .mli file. But, it will detect +# dead code in inlined modules, even if they have a signature. This suggests the following idea, +# which is basically what happens here: +# +# 1. Put all of the infer source code into a single .ml file with an empty .mli. Each file is put +# inside its own inline module, with its original signature included too to avoid ambiguities in +# case of locally opened modules (which may shadow more values than intended otherwise). +# 2. Add preprocessor instructions so that OCaml warnings are shown in the original files. +# 3. Suppress warnings in source code copied from generated files (atdgen, lexers, and parsers). +# 3. Run the OCaml compiler. +# 4. Kill detected dead code; repeat until dry. +# +# Infer is first compiled so that atdgen, ocamllex, and menhir generate the appropriate files. The +# ocamllex and menhir files are generated inside ../_build by jbuilder, and copied here by this +# Makefile. +# +# ocamldepend is used to `cat` the source files in the right order into all_infer_in_one_file.ml. +# +# Beware that this is mostly a terrible hack. + +ROOT_DIR = ../../.. +include $(ROOT_DIR)/Makefile.config + +default: detect_dead_code + +ml_src_files_from_mlly:=$(shell find .. -not -path "../*stubs*" -regex '\.\./[a-zA-Z].*\.ml\(l\|y\)') + +ml_src_files:=$(shell cd .. && find . -not -path "./*stubs*" -regex '\./[a-zA-Z].*\.ml\(i\)*') + +ml_src_files_without_mli:=$(shell cd .. && for i in */*.ml */*/*.ml; do [ -f $${i}i ] || echo $$i; done) + +.PHONY: depend +depend: + cd .. && \ + ocamldep -native \ + -I IR -I absint -I atd -I backend -I base -I bufferoverrun -I checkers \ + -I clang -I concurrency -I eradicate -I facebook -I integration -I istd \ + -I java -I labs -I python -I quandary -I unit -I unit/clang -I deadcode \ + $(ml_src_files) > deadcode/.depend + +%.cmi: +# deal with the .ml *and* the .mli at the same time: when all the modules are inlined in one +# file, you need the module value to be defined before you can refer to it, even in +# signatures. Because of this, the order given by ocamldep is not enough to avoid "Unbound +# module MyModule" errors in the case of signatures referring to other modules. + $(QUIET)echo "(* START OF SIGNATURE $*.mli *)" >> all_infer_in_one_file.ml +# put too many spaces in general but you never know what the contents of the file is; +# sometimes spaces will be needed + $(QUIET)echo " module type " >> all_infer_in_one_file.ml +# suppress some warnings for generated code + $(QUIET)if [[ $@ =~ (atd|deadcode)/ ]]; then echo ' [@warning "-27-32-34-35-39"] ' >> all_infer_in_one_file.ml; fi +# compute module name from file name: capitalize first letter + $(QUIET)echo $(shell basename $*) | sed -e "s/\b\(.\)/ \u\1/g" >> all_infer_in_one_file.ml + $(QUIET)echo " = sig " >> all_infer_in_one_file.ml +# pre-processor directive to get errors in the original files and not in all_infer_in_one_file.ml + $(QUIET)echo '# 1 "$*.mli"' >> all_infer_in_one_file.ml + cat ../$*.mli >> all_infer_in_one_file.ml + $(QUIET)echo " end " >> all_infer_in_one_file.ml + $(QUIET)echo "(* END OF SIGNATURE $*.mli *)" >> all_infer_in_one_file.ml + $(QUIET)echo >> all_infer_in_one_file.ml +# lots of duplication from above, sorry + $(QUIET)echo "(* START OF MODULE $*.ml *)" >> all_infer_in_one_file.ml + $(QUIET)echo " module " >> all_infer_in_one_file.ml + $(QUIET)if [[ $@ =~ (atd|deadcode)/ ]]; then echo ' [@warning "-27-32-34-35-39"] ' >> all_infer_in_one_file.ml; fi + $(QUIET)echo $(shell basename $*) | sed -e "s/\b\(.\)/ \u\1/g" >> all_infer_in_one_file.ml + $(QUIET)echo " : " >> all_infer_in_one_file.ml + $(QUIET)echo $(shell basename $*) | sed -e "s/\b\(.\)/ \u\1/g" >> all_infer_in_one_file.ml + $(QUIET)echo " = struct " >> all_infer_in_one_file.ml + $(QUIET)echo '# 1 "$*.ml"' >> all_infer_in_one_file.ml + cat ../$*.ml >> all_infer_in_one_file.ml + $(QUIET)echo " end " >> all_infer_in_one_file.ml + $(QUIET)echo "(* END OF MODULE $*.ml *)" >> all_infer_in_one_file.ml + $(QUIET)echo >> all_infer_in_one_file.ml + +$(ml_src_files_without_mli:.ml=.cmx): +# again mostly duplicated from above + $(QUIET)echo "(* START OF MODULE $(@) *)" >> all_infer_in_one_file.ml + $(QUIET)echo " module " >> all_infer_in_one_file.ml + $(QUIET)if [[ $@ =~ (atd|deadcode)/ ]]; then echo ' [@warning "-27-32-34-35-39"] ' >> all_infer_in_one_file.ml; fi + $(QUIET)echo $(shell basename $@ .cmx) | sed -e "s/\b\(.\)/ \u\1/g" >> all_infer_in_one_file.ml + $(QUIET)echo " = struct " >> all_infer_in_one_file.ml + $(QUIET)echo "# 1 \"$$(echo $@ | sed -e 's/\.cmx$$/.ml/')\"" >> all_infer_in_one_file.ml + cat ../$$(echo $@ | sed -e "s/\.cmx$$/.ml/") >> all_infer_in_one_file.ml + $(QUIET)echo " end " >> all_infer_in_one_file.ml + $(QUIET)echo "(* END OF MODULE $@ *)" >> all_infer_in_one_file.ml + $(QUIET)echo >> all_infer_in_one_file.ml + +-include .depend + +.PHONY: all_infer_in_one_file.ml +all_infer_in_one_file.ml: backend/infer.cmx unit/inferunit.cmx facebook/InferCreateTraceViewLinks.cmx + $(QUIET)echo "see results in all_infer_in_one_file.ml" + +.PHONY: detect_dead_code +detect_dead_code: + $(MAKE) clean +# needed to get jbuild generated, and the generated code for the lexers and parsers in ../_build + $(MAKE) -C .. byte +# copy generated source files from ../_build + for file in $(ml_src_files_from_mlly); do \ + set +e; \ + [ -f "../_build/default/$$(basename $$file .mly).ml" ] && \ + $(COPY) ../_build/default/$$(basename $$file .mly).ml .; \ + [ -f "../_build/default/$$(basename $$file .mly).mli" ] && \ + $(COPY) ../_build/default/$$(basename $$file .mly).mli .; \ + [ -f "../_build/default/$$(basename $$file .mll).ml" ] && \ + $(COPY) ../_build/default/$$(basename $$file .mll).ml .; \ + [ -f "../_build/default/$$(basename $$file .mll).mli" ] && \ + $(COPY) ../_build/default/$$(basename $$file .mll).mli .; \ + set -e; \ + done + $(REMOVE) all_infer_in_one_file.ml + $(MAKE) depend +# Need to be sequential to avoid getting a garbled file. Need to re-include .depend as it may +# have changed, so run another `make`. + $(MAKE) -j 1 all_infer_in_one_file.ml +# build and get dead code warnings + jbuilder build ../_build/default/deadcode/all_infer_in_one_file.bc +# be paranoid about cleaning because we do not want to include infer_in_one_file into infer by +# accident and I don't know enough jbuilder to be positive that it won't happen + $(MAKE) clean + +.PHONY: clean +clean: + $(REMOVE) .depend *.ml *.mli jbuild + touch all_infer_in_one_file.mli all_infer_in_one_file.ml diff --git a/infer/src/deadcode/all_infer_in_one_file.ml b/infer/src/deadcode/all_infer_in_one_file.ml new file mode 100644 index 000000000..e69de29bb diff --git a/infer/src/deadcode/all_infer_in_one_file.mli b/infer/src/deadcode/all_infer_in_one_file.mli new file mode 100644 index 000000000..e69de29bb diff --git a/infer/src/deadcode/jbuild.in b/infer/src/deadcode/jbuild.in new file mode 100644 index 000000000..a2e5dff37 --- /dev/null +++ b/infer/src/deadcode/jbuild.in @@ -0,0 +1,18 @@ +(* -*- tuareg -*- *) +(* NOTE: prepend jbuild.common to this file! *) + +;; Format.sprintf + {| +(executable + ((name all_infer_in_one_file) + (flags (%s)) + (ocamlopt_flags (%s)) + (libraries (%s)) + (modules (All_infer_in_one_file)) + (preprocess (pps (ppx_compare ppx_sexp_conv -no-check))) + )) +|} + (String.concat " " common_cflags) + (String.concat " " common_optflags) + (String.concat " " common_libraries) + |> Jbuild_plugin.V1.send diff --git a/scripts/dead_code_detection.sh b/scripts/dead_code_detection.sh new file mode 100755 index 000000000..5e2829ca1 --- /dev/null +++ b/scripts/dead_code_detection.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright (c) 2018 - present Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the BSD style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + + +# Dead code detection for infer's source code. See comments in infer/src/deadcode/Makefile. + +set -e +set -o pipefail +set -u + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +make -C "$SCRIPT_DIR"/../infer/src/deadcode