diff --git a/.gitignore b/.gitignore index ef8f18a97..6fe2e3ef0 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ buck-out/ /infer/lib/specs/clean_models /scripts/checkCopyright /infer/etc/clang_ast.dict +/infer/src/toplevel.mlpack #atdgen stubs /infer/src/backend/jsonbug_* diff --git a/Makefile b/Makefile index b6cf0a895..279cf58cf 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,10 @@ check_missing_mli: for x in `find infer/src -name "*.ml"`; do \ test -f "$$x"i || echo Missing "$$x"i; done' -test: test_build ocaml_unit_test buck_test inferTraceBugs_test +inferScriptMode_test: toplevel + INFER_REPL_BINARY=ocaml ./scripts/infer_repl ./infer/tests/repl/infer_batch_script.ml + +test: test_build ocaml_unit_test buck_test inferTraceBugs_test inferScriptMode_test $(MAKE) -C $(SRC_DIR) mod_dep.dot test_xml: test_build ocaml_unit_test buck_test_xml inferTraceBugs_test @@ -133,6 +136,9 @@ test_xml: test_build ocaml_unit_test buck_test_xml inferTraceBugs_test quick-test: test_this_build ocaml_unit_test +toplevel: + $(MAKE) -C $(SRC_DIR) toplevel + uninstall: $(REMOVE_DIR) $(DESTDIR)$(libdir)/infer/ $(REMOVE) $(DESTDIR)$(bindir)/inferTraceBugs @@ -274,7 +280,7 @@ conf-clean: clean .PHONY: all buck_test buck_test_xml clean clang_plugin clang_setup infer inferTraceBugs .PHONY: inferTraceBugs_test install ocaml_unit_test check_missing_mli src_build test test_xml -.PHONY: test_build uninstall +.PHONY: test_build toplevel uninstall # print any variable for Makefile debugging diff --git a/infer/src/Makefile b/infer/src/Makefile index c8c39bc43..d954f9de2 100644 --- a/infer/src/Makefile +++ b/infer/src/Makefile @@ -169,7 +169,7 @@ INFER_CONFIG_TARGETS += $(INFERLLVM_MAIN).native DEPENDENCIES += llvm endif -.PHONY: all infer init version sanitize clean +.PHONY: all infer init version sanitize clean toplevel all: infer @@ -251,6 +251,27 @@ mod_dep.dot: $(DEPENDENCIES_DIR)/ocamldot/ocamldot $(ml_src_files) $(re_src_file mod_dep.pdf: mod_dep.dot dot -Tpdf -o mod_dep.pdf mod_dep.dot +roots_grep_regex:=$(foreach root,$(roots),-e $(root)$$) +dirs_find_regex:=$(foreach dir, $(DEPENDENCIES),-path "./$(dir)/*" -o) +toplevel: init $(STACKTREE_ATDGEN_STUBS) $(INFERPRINT_ATDGEN_STUBS) $(CLANG_ATDGEN_STUBS) $(INFER_CLANG_FCP_MIRRORED_FILES) + # We need to pack all infer modules into another module to avoid name clashes + # with some of them coming from ocaml libraries (Ident for example). + # To do that, we generate .mlpack file with source files. Steps: + # 1. find all interesting .re and .ml files - they need to be in one of + # directories listed in $(DEPENDENCIES) + # 2. remove './' from the beginning of each line + # 3. remove extension from all files + # 4. make first letter of filename uppercase to produce valid ocaml module + # 5. filter out root modules since they run code when loading the module. + find . \( -name "*.ml" -o -name "*.mly" -o -name "*.mll" -o -name "*.re" \) \ + \( $(dirs_find_regex) -false \) \ + -not \( -path "./unit/*" -o -path "./llvm/*" -o -path "./facebook/scripts/eradicate_stats.ml" \) \ + | cut -c 3- \ + | rev | cut -f 2- -d '.' | rev \ + | awk 'BEGIN { FS = "/"; OFS = "/" } ; {$$NF=toupper(substr($$NF,1,1))substr($$NF,2); print $$0}' \ + | grep -v $(roots_grep_regex) > toplevel.mlpack + $(OCAMLBUILD_ALL) -build-dir $(INFER_BUILD_DIR) toplevel.cmo + define gen_atdgen_rules # generate files using atdgen # parameters: diff --git a/infer/src/backend/CommandLineOption.ml b/infer/src/backend/CommandLineOption.ml index eefa288f5..c17603ce9 100644 --- a/infer/src/backend/CommandLineOption.ml +++ b/infer/src/backend/CommandLineOption.ml @@ -17,7 +17,7 @@ module YBU = Yojson.Basic.Util (** Each command line option may appear in the --help list of any executable, these tags are used to specify which executables for which an option will be documented. *) -type exe = Analyze | Clang | Java | Llvm | Print | StatsAggregator | Toplevel +type exe = Analyze | Clang | Java | Llvm | Print | StatsAggregator | Toplevel | Interactive let exes = [ ("InferAnalyze", Analyze); @@ -27,13 +27,16 @@ let exes = [ ("InferPrint", Print); ("InferStatsAggregator", StatsAggregator); ("infer", Toplevel); + ("interactive", Interactive); ] let all_exes = IList.map snd exes let current_exe = - try IList.assoc string_equal (Filename.basename Sys.executable_name) exes - with Not_found -> Toplevel + if !Sys.interactive then Interactive + else + try IList.assoc string_equal (Filename.basename Sys.executable_name) exes + with Not_found -> Toplevel type desc = { @@ -442,7 +445,7 @@ let decode_env_to_argv env = let prepend_to_argv args = let cl_args = match Array.to_list Sys.argv with _ :: tl -> tl | [] -> [] in - (Sys.executable_name, args @ cl_args) + args @ cl_args (** [prefix_before_rest (prefix @ ["--" :: rest])] is [prefix] where "--" is not in [prefix]. *) let prefix_before_rest args = @@ -553,7 +556,10 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file env_var exe_u (try Unix.getenv "INFERCLANG_ARGS" with Not_found -> "") in let env_args = c_args @ env_args in (* end transitional support for INFERCLANG_ARGS *) - let exe_name, env_cl_args = prepend_to_argv env_args in + let exe_name = Sys.executable_name in + let env_cl_args = match current_exe with + | Interactive -> env_args + | _ -> prepend_to_argv env_args in let all_args = match config_file with | None -> env_cl_args | Some path -> @@ -575,7 +581,7 @@ let parse ?(incomplete=false) ?(accept_unknown=false) ?config_file env_var exe_u | Arg.Bad usage_msg -> Pervasives.prerr_string usage_msg; exit 2 | Arg.Help usage_msg -> Pervasives.print_string usage_msg; exit 0 in - parse_loop () ; + parse_loop (); if not incomplete then Unix.putenv env_var (encode_argv_to_env (prefix_before_rest all_args)) ; curr_usage diff --git a/infer/src/backend/CommandLineOption.mli b/infer/src/backend/CommandLineOption.mli index 71562fdbb..cd9f036f1 100644 --- a/infer/src/backend/CommandLineOption.mli +++ b/infer/src/backend/CommandLineOption.mli @@ -11,7 +11,7 @@ open! Utils -type exe = Analyze | Clang | Java | Llvm | Print | StatsAggregator | Toplevel +type exe = Analyze | Clang | Java | Llvm | Print | StatsAggregator | Toplevel | Interactive val current_exe : exe diff --git a/infer/src/backend/config.ml b/infer/src/backend/config.ml index 86b57d486..1996045b7 100644 --- a/infer/src/backend/config.ml +++ b/infer/src/backend/config.ml @@ -1183,6 +1183,8 @@ let exe_usage (exe : CLOpt.exe) = Aggregates all the perf stats generated by Buck on each target" | Toplevel -> version_string + | Interactive -> + "Usage: interactive ocaml toplevel. To pass infer config options use env variable" let post_parsing_initialization () = F.set_margin !margin ; diff --git a/infer/tests/repl/infer_batch_script.ml b/infer/tests/repl/infer_batch_script.ml new file mode 100644 index 000000000..5ba8262ac --- /dev/null +++ b/infer/tests/repl/infer_batch_script.ml @@ -0,0 +1,22 @@ +(* + * Copyright (c) 2016 - 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. + *) + +(* Example of ocaml script starting with infer code. To execute a scipt run: + ./scripts/infer_repl + It's used as a basic integration test *) + +(* "import" infer code *) +#use "toplevel_init";; + +let _ = Ident.create_fresh Ident.knormal in +let ident = Ident.create_fresh Ident.knormal in +let e = Exp.Var ident in +print_endline (Sil.exp_to_string e); +(* pass --flavors flag to change the value *) +print_endline (string_of_bool Config.flavors) diff --git a/scripts/infer_repl b/scripts/infer_repl new file mode 100755 index 000000000..1863067bf --- /dev/null +++ b/scripts/infer_repl @@ -0,0 +1,20 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +BUILD_DIR=$SCRIPT_DIR/../infer/_build/infer +# to build new toplevel, run `make toplevel` +# -init option is used only in interactive mode +# in batch mode, scripts need to import toplevel_init themselves +# It can be done by adding #use "toplevel_init";; to the beginning +# of a script. +# NOTE: $SCRIPT_DIR is added search path for batch scripts +# so they can be located anywhere and still find toplevel_init +# file. In interactive mode $SCRIPT_DIR isn't needed + +# by default utop is used, pass `INFER_UTOP_BINARY` to change the toplevel +# binary (to `ocaml` for example) +if [ -z "$INFER_UTOP_BINARY" ]; then + INFER_REPL_BINARY="utop" +fi +$INFER_REPL_BINARY -init $SCRIPT_DIR/toplevel_init -I $BUILD_DIR -I $SCRIPT_DIR $@ diff --git a/scripts/toplevel_init b/scripts/toplevel_init new file mode 100644 index 000000000..e8bd9ec34 --- /dev/null +++ b/scripts/toplevel_init @@ -0,0 +1,9 @@ +(* load dependencies *) +#use "topfind";; +#require "sawja";; +#require "atdgen";; +#require "extlib";; + +(* load infer code *) +#load_rec "toplevel.cmo";; +open Toplevel;;