From 679b125ac4f381bea4d3cadaed7ace391856198f Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Fri, 4 Aug 2017 04:37:40 -0700 Subject: [PATCH] [trace] infer subcommand for inferTraceBugs Summary: Replace `inferTraceBugs` with `infer-explore` with a similar CLI. Some options changed: - --max-level -> --max-nesting, and "max" is the default value instead of a possible value - --no-source -> --no-source-preview Reviewed By: mbouaziz Differential Revision: D5526651 fbshipit-source-id: 8383f37 --- .gitignore | 1 + FILES.md | 2 - Makefile | 3 -- Makefile.config | 3 +- infer/lib/python/inferTraceBugs | 27 ++++-------- infer/lib/python/inferlib/issues.py | 2 +- infer/src/backend/infer.ml | 18 ++++++++ infer/src/base/CommandDoc.ml | 21 +++++++--- infer/src/base/CommandLineOption.ml | 3 +- infer/src/base/CommandLineOption.mli | 1 + infer/src/base/Config.ml | 41 +++++++++++++++++-- infer/src/base/Config.mli | 10 +++++ infer/tests/build_systems/tracebugs/Makefile | 22 +++++----- .../build_systems/utf8_in_procname/Makefile | 6 +-- 14 files changed, 109 insertions(+), 51 deletions(-) diff --git a/.gitignore b/.gitignore index 86e579b2c..584e2abf7 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ buck-out/ /infer/bin/infer-analyze /infer/bin/infer-capture /infer/bin/infer-compile +/infer/bin/infer-explore /infer/bin/infer-report /infer/bin/infer-reportdiff /infer/bin/infer-run diff --git a/FILES.md b/FILES.md index b2a2b7c24..b5abcab16 100644 --- a/FILES.md +++ b/FILES.md @@ -5,5 +5,3 @@ *infer* : Main command to run Infer. Check out the docs for instructions on how to use it. *infer-* : Infer subcommands. Running `infer- [options]` is the same as running `infer [options]`. - -*inferTraceBugs* : Python script to explore the error traces in Infer reports diff --git a/Makefile b/Makefile index 205c0800c..5cd1f38b3 100644 --- a/Makefile +++ b/Makefile @@ -469,9 +469,6 @@ endif (cd $(DESTDIR)$(libdir)/infer/infer/bin && \ $(REMOVE) $$alias && \ $(LN_S) infer $$alias); done - (cd $(DESTDIR)$(bindir)/ && \ - $(REMOVE) inferTraceBugs && \ - $(LN_S) $(libdir_relative_to_bindir)/infer/infer/lib/python/inferTraceBugs inferTraceBugs) $(QUIET)for i in $(MAN_DIR)/man1/*; do \ $(INSTALL_DATA) -C $$i $(DESTDIR)$(mandir)/man1/$$(basename $$i); \ done diff --git a/Makefile.config b/Makefile.config index 6dc626b15..7a36026d4 100644 --- a/Makefile.config +++ b/Makefile.config @@ -60,14 +60,13 @@ INFER_COMMANDS = \ infer-analyze \ infer-capture \ infer-compile \ + infer-explore \ infer-report \ infer-reportdiff \ infer-run \ -INFERTRACEBUGS_BIN = $(BIN_DIR)/inferTraceBugs INFER_CREATE_TRACEVIEW_LINKS = InferCreateTraceViewLinks INFER_CREATE_TRACEVIEW_LINKS_BIN = $(BIN_DIR)/$(INFER_CREATE_TRACEVIEW_LINKS) -INFERTRACEBUGS_BIN_RELPATH = infer/bin/inferTraceBugs INFER_COMMAND_MANUALS = $(INFER_COMMANDS:%=$(MAN_DIR)/man1/%.1) INFER_MANUAL = $(MAN_DIR)/man1/infer.1 diff --git a/infer/lib/python/inferTraceBugs b/infer/lib/python/inferTraceBugs index f07c2c093..7c74ecf53 100755 --- a/infer/lib/python/inferTraceBugs +++ b/infer/lib/python/inferTraceBugs @@ -45,15 +45,14 @@ base_parser.add_argument('--no-source', help='Do not print code excerpts') base_parser.add_argument('--select', metavar='N', - nargs=1, + type=int, help='Select bug number N. ' 'If omitted, prompts you for input.') base_parser.add_argument('--max-level', metavar='N', - nargs=1, + type=int, help='Level of nested procedure calls to show. ' - 'Can be "max", in which case all levels are shown. ' - 'If omitted, prompts you for input.') + 'By default, all levels are shown.') base_parser.add_argument('--html', action='store_true', help='Generate HTML report.') @@ -159,7 +158,7 @@ class Selector(object): def prompt_report(self): report_number = 0 if self.args.select is not None: - report_number = self.parse_report_number(self.args.select[0], True) + report_number = self.parse_report_number(self.args.select, True) else: self.show_choices() @@ -174,19 +173,7 @@ class Selector(object): return self.reports[report_number] def prompt_level(self): - if self.args.max_level is not None: - return self.parse_max_level(self.args.max_level[0], True) - - max_level_str = raw_input( - 'Choose maximum level of nested procedures calls (default=max): ') - if max_level_str == '': - max_level = sys.maxsize - else: - max_level = self.parse_max_level(max_level_str) - - print('') - - return max_level + return self.parse_max_level(self.args.max_level, True) def parse_report_number(self, s, show_help=False): try: @@ -202,14 +189,14 @@ class Selector(object): return n def parse_max_level(self, s, show_help=False): - if s == 'max': + if s is None: return sys.maxsize try: n = int(s) except ValueError: show_error_and_exit( - 'ERROR: integer max level or "max" expected', + 'ERROR: integer max level expected', show_help) if n < 0: diff --git a/infer/lib/python/inferlib/issues.py b/infer/lib/python/inferlib/issues.py index 3c44be065..60a503839 100644 --- a/infer/lib/python/inferlib/issues.py +++ b/infer/lib/python/inferlib/issues.py @@ -157,7 +157,7 @@ def _text_of_report_list(project_root, reports, bugs_txt_path, limit=None, if limit >= 0 and n_issues > limit: text_errors += colorize.color( ('\n\n...too many issues to display (limit=%d exceeded), please ' + - 'see %s or run `inferTraceBugs` for the remaining issues.') + 'see %s or run `infer-explore` for the remaining issues.') % (limit, bugs_txt_path), colorize.HEADER, formatter) issues_found = 'Found {n_issues}'.format( diff --git a/infer/src/backend/infer.ml b/infer/src/backend/infer.ml index 7e680d0ee..fe169a5ec 100644 --- a/infer/src/backend/infer.ml +++ b/infer/src/backend/infer.ml @@ -73,6 +73,8 @@ let setup_results_dir () = || Config.(buck || continue_capture || maven || reactive_mode) ) then remove_results_dir () ; create_results_dir () + | Explore + -> assert_results_dir "please run an infer analysis first" let () = if Config.print_builtins then Builtin.print_and_exit () ; @@ -119,3 +121,19 @@ let () = -> run (Lazy.force Driver.mode_from_command_line) | Diff -> Diff.diff (Lazy.force Driver.mode_from_command_line) + | Explore + -> let if_some key opt args = + match opt with None -> args | Some arg -> key :: string_of_int arg :: args + in + let if_true key opt args = if not opt then args else key :: args in + let if_false key opt args = if opt then args else key :: args in + let args = + if_some "--max-level" Config.max_nesting @@ if_true "--only-show" Config.only_show + @@ if_false "--no-source" Config.source_preview @@ if_true "--html" Config.html + @@ if_some "--select" Config.select ["-o"; Config.results_dir] + in + let prog = Config.lib_dir ^/ "python" ^/ "inferTraceBugs" in + if is_error (Unix.waitpid (Unix.fork_exec ~prog ~argv:(prog :: args) ())) then + L.external_error + "** Error running the reporting script:@\n** %s %s@\n** See error above@." prog + (String.concat ~sep:" " args) diff --git a/infer/src/base/CommandDoc.ml b/infer/src/base/CommandDoc.ml index b8bd9b1db..431faecb4 100644 --- a/infer/src/base/CommandDoc.ml +++ b/infer/src/base/CommandDoc.ml @@ -24,6 +24,7 @@ let command_to_name = ; (Capture, "capture") ; (Compile, "compile") ; (Diff, "diff") + ; (Explore, "explore") ; (Report, "report") ; (ReportDiff, "reportdiff") ; (Run, "run") ] @@ -38,8 +39,8 @@ let command_of_exe_name exe_name = List.find_map command_to_name ~f:(fun (cmd, name) -> if String.equal exe_name (exe_name_of_command_name name) then Some cmd else None ) -let mk_command_doc ~see_also:see_also_commands ?and_also ?environment:environment_opt - ?files:files_opt ~synopsis = +let mk_command_doc ~see_also:see_also_commands ?environment:environment_opt ?files:files_opt + ~synopsis = let section = 1 in let see_also = let exe_names = @@ -47,8 +48,7 @@ let mk_command_doc ~see_also:see_also_commands ?and_also ?environment:environmen let exe = exe_name_of_command cmd in Printf.sprintf "$(b,%s)(%d)" (Cmdliner.Manpage.escape exe) section ) in - let suffix = Option.value ~default:"" and_also in - [`P (String.concat ~sep:", " exe_names ^ suffix)] + [`P (String.concat ~sep:", " exe_names)] in let environment = Option.value environment_opt @@ -133,6 +133,16 @@ let diff = ~description:[`P "EXPERIMENTAL AND IN NO WAY READY TO USE"] ~see_also:CLOpt.([ReportDiff; Run]) +let explore = + mk_command_doc ~title:"Infer Explore" + ~short_description:"explore the error traces in infer reports" + ~synopsis:{|$(b,infer) $(b,explore) $(i,[options])|} + ~description: + [ `P + "Show the list of bugs on the console and explore symbolic program traces emitted by infer to explain a report. Can also generate an HTML report from a JSON report." + ] + ~see_also:CLOpt.([Report; Run]) + let infer = mk_command_doc ~title:"Infer Static Analyzer" ~short_description:"static analysis for Java and C/C++/Objective-C/Objective-C++" @@ -211,7 +221,7 @@ $(b,infer) $(i,[options])|} }|} ] ~see_also:(List.filter ~f:(function CLOpt.Clang -> false | _ -> true) CLOpt.all_commands) - ~and_also:", $(b,inferTraceBugs)" "infer" + "infer" let report = mk_command_doc ~title:"Infer Reporting" ~short_description:"compute and manipulate infer results" @@ -267,6 +277,7 @@ let command_to_data = ; mk Capture capture ; mk Compile compile ; mk Diff diff + ; mk Explore explore ; mk Report report ; mk ReportDiff reportdiff ; mk Run run ] diff --git a/infer/src/base/CommandLineOption.ml b/infer/src/base/CommandLineOption.ml index b6ad255d5..bec4e2a4f 100644 --- a/infer/src/base/CommandLineOption.ml +++ b/infer/src/base/CommandLineOption.ml @@ -90,6 +90,7 @@ type command = | Clang | Compile | Diff + | Explore | Report | ReportDiff | Run @@ -97,7 +98,7 @@ type command = let equal_command = [%compare.equal : command] -let all_commands = [Analyze; Capture; Clang; Compile; Diff; Report; ReportDiff; Run] +let all_commands = [Analyze; Capture; Clang; Compile; Diff; Explore; Report; ReportDiff; Run] type command_doc = { title: Cmdliner.Manpage.title diff --git a/infer/src/base/CommandLineOption.mli b/infer/src/base/CommandLineOption.mli index 6d4e08ba7..e502f1d0c 100644 --- a/infer/src/base/CommandLineOption.mli +++ b/infer/src/base/CommandLineOption.mli @@ -34,6 +34,7 @@ type command = (** set up the infer environment then run the compilation commands without capturing the source files *) | Diff (** orchestrate a diff analysis *) + | Explore (** explore infer reports *) | Report (** post-process infer results and reports *) | ReportDiff (** compute the difference of two infer reports *) | Run (** orchestrate the capture, analysis, and reporting of a compilation command *) diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 3ec3ef33c..9bc87a0aa 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -420,7 +420,7 @@ let startup_action = match initial_command with | Some Clang -> NoParse - | None | Some (Analyze | Capture | Compile | Diff | Report | ReportDiff | Run) + | None | Some (Analyze | Capture | Compile | Diff | Explore | Report | ReportDiff | Run) -> InferCommand let exe_usage = @@ -474,7 +474,7 @@ let () = -> assert false (* filtered out *) | Report -> `Add - | Analyze | Capture | Compile | Diff | ReportDiff | Run + | Analyze | Capture | Compile | Diff | Explore | ReportDiff | Run -> `Reject in (* make sure we generate doc for all the commands we know about *) @@ -871,7 +871,8 @@ and ( bo_debug , write_dotty ) = let all_generic_manuals = List.filter_map CLOpt.all_commands ~f:(fun cmd -> - if CLOpt.(equal_command cmd Clang) then None else Some (cmd, manual_generic) ) + if List.mem ~equal:CLOpt.equal_command CLOpt.([Clang; Explore]) cmd then None + else Some (cmd, manual_generic) ) in let bo_debug = CLOpt.mk_int ~default:0 ~long:"bo-debug" @@ -1159,6 +1160,9 @@ and help_format = ~in_help:(List.map CLOpt.all_commands ~f:(fun command -> (command, manual_generic))) "Show this help in the specified format. $(b,auto) sets the format to $(b,plain) if the environment variable $(b,TERM) is \"dumb\" or undefined, and to $(b,pager) otherwise." +and html = + CLOpt.mk_bool ~long:"html" ~in_help:CLOpt.([(Explore, manual_generic)]) "Generate html report." + and icfg_dotty_outfile = CLOpt.mk_path_opt ~long:"icfg-dotty-outfile" ~meta:"path" "If set, specifies path where .dot file should be written, it overrides the path for all other options that would generate icfg file otherwise" @@ -1260,6 +1264,11 @@ and margin = CLOpt.mk_int ~deprecated:["set_pp_margin"] ~long:"margin" ~default:100 ~meta:"int" "Set right margin for the pretty printing functions" +and max_nesting = + CLOpt.mk_int_opt ~long:"max-nesting" + ~in_help:CLOpt.([(Explore, manual_generic)]) + "Level of nested procedure calls to show. Trace elements beyond the maximum nesting level are skipped. If omitted, all levels are shown." + and merge = CLOpt.mk_bool ~deprecated:["merge"] ~long:"merge" ~in_help:CLOpt.([(Analyze, manual_buck_flavors)]) @@ -1294,6 +1303,11 @@ and objc_memory_model = and only_footprint = CLOpt.mk_bool ~deprecated:["only_footprint"] ~long:"only-footprint" "Skip the re-execution phase" +and only_show = + CLOpt.mk_bool ~long:"only-show" + ~in_help:CLOpt.([(Explore, manual_generic)]) + "Show the list of reports and exit" + and passthroughs = CLOpt.mk_bool ~long:"passthroughs" ~default:false "In error traces, show intermediate steps that propagate data. When false, error traces are shorter and show only direct flow via souces/sinks" @@ -1459,6 +1473,7 @@ and results_dir = (CLOpt.( [ (Analyze, manual_generic) ; (Capture, manual_generic) + ; (Explore, manual_generic) ; (Run, manual_generic) ; (Report, manual_generic) ])) ~meta:"dir" "Write results and internal files in the specified directory" @@ -1472,6 +1487,11 @@ and seconds_per_iteration = CLOpt.mk_float_opt ~deprecated:["seconds_per_iteration"] ~long:"seconds-per-iteration" ~meta:"float" "Set the number of seconds per iteration (see $(b,--iterations))" +and select = + CLOpt.mk_int_opt ~long:"select" ~meta:"N" + ~in_help:CLOpt.([(Explore, manual_generic)]) + "Select bug number $(i,N). If omitted, prompt for input." + and siof_safe_methods = CLOpt.mk_string_list ~long:"siof-safe-methods" ~in_help:CLOpt.([(Analyze, manual_siof)]) @@ -1498,6 +1518,11 @@ and skip_translation_headers = ~in_help:CLOpt.([(Capture, manual_clang)]) ~meta:"path prefix" "Ignore headers whose path matches the given prefix" +and source_preview = + CLOpt.mk_bool ~long:"source-preview" ~default:true + ~in_help:CLOpt.([(Explore, manual_generic)]) + "print code excerpts around trace elements" + and sources = CLOpt.mk_string_list ~long:"sources" "Specify the list of source files" and sourcepath = CLOpt.mk_string_opt ~long:"sourcepath" "Specify the sourcepath" @@ -1978,6 +2003,8 @@ and frontend_stats = !frontend_stats and headers = !headers +and html = !html + and icfg_dotty_outfile = !icfg_dotty_outfile and ignore_trivial_traces = !ignore_trivial_traces @@ -2027,6 +2054,8 @@ and log_file = !log_file and makefile_cmdline = !makefile +and max_nesting = !max_nesting + and merge = !merge and ml_buckets = !ml_buckets @@ -2049,6 +2078,8 @@ and only_cheap_debug = !only_cheap_debug and only_footprint = !only_footprint +and only_show = !only_show + and passthroughs = !passthroughs and patterns_never_returning_null = @@ -2131,6 +2162,8 @@ and save_analysis_results = !save_results and seconds_per_iteration = !seconds_per_iteration +and select = !select + and show_buckets = !print_buckets and show_progress_bar = !progress_bar @@ -2147,6 +2180,8 @@ and skip_duplicated_types = !skip_duplicated_types and skip_translation_headers = !skip_translation_headers +and source_preview = !source_preview + and sources = !sources and sourcepath = !sourcepath diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 30c1ea631..4f9561022 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -411,6 +411,8 @@ val generated_classes : string option val headers : bool +val html : bool + val icfg_dotty_outfile : string option val ignore_trivial_traces : bool @@ -480,6 +482,8 @@ val makefile_cmdline : string val maven : bool +val max_nesting : int option + val merge : bool val ml_buckets : @@ -501,6 +505,8 @@ val only_cheap_debug : bool val only_footprint : bool +val only_show : bool + val pmd_xml : bool val precondition_stats : bool @@ -565,6 +571,8 @@ val save_analysis_results : string option val seconds_per_iteration : float option +val select : int option + val show_buckets : bool val show_progress_bar : bool @@ -581,6 +589,8 @@ val skip_duplicated_types : bool val skip_translation_headers : string list +val source_preview : bool + val spec_abs_level : int val specs_library : string list diff --git a/infer/tests/build_systems/tracebugs/Makefile b/infer/tests/build_systems/tracebugs/Makefile index e5eca6497..29582c266 100644 --- a/infer/tests/build_systems/tracebugs/Makefile +++ b/infer/tests/build_systems/tracebugs/Makefile @@ -20,26 +20,26 @@ infer-out/report.json: $(SOURCES) $(CLANG_DEPS) .PHONY: test1 test1: infer-out/report.json - $(QUIET)$(call silent_on_success,Testing inferTraceBugs: --max-level=max,\ - $(PYTHON_DIR)/inferTraceBugs -o infer-out \ - --select 0 --max-level max) + $(QUIET)$(call silent_on_success,Testing infer-explore: --max-nesting=3,\ + $(INFER_BIN) explore -o infer-out \ + --select 0 --max-nesting 3) .PHONY: test2 test2: infer-out/report.json - $(QUIET)$(call silent_on_success,Testing inferTraceBugs: --max-level=0,\ - $(PYTHON_DIR)/inferTraceBugs -o infer-out \ - --select 0 --max-level 0) + $(QUIET)$(call silent_on_success,Testing infer-explore: --max-nesting=0,\ + $(INFER_BIN) explore -o infer-out \ + --select 0 --max-nesting 0) .PHONY: test3 test3: infer-out/report.json - $(QUIET)$(call silent_on_success,Testing inferTraceBugs: --max-level=max --no-source,\ - $(PYTHON_DIR)/inferTraceBugs -o infer-out \ - --select 0 --max-level max --no-source) + $(QUIET)$(call silent_on_success,Testing infer-explore: --no-source-preview,\ + $(INFER_BIN) explore -o infer-out \ + --select 0 --no-source-preview) .PHONY: test4 test4: infer-out/report.json - $(QUIET)$(call silent_on_success,Testing inferTraceBugs: --only-show,\ - $(PYTHON_DIR)/inferTraceBugs -o infer-out \ + $(QUIET)$(call silent_on_success,Testing infer-explore: --only-show,\ + $(INFER_BIN) explore -o infer-out \ --only-show) diff --git a/infer/tests/build_systems/utf8_in_procname/Makefile b/infer/tests/build_systems/utf8_in_procname/Makefile index 142c4afd6..ddf1ef8f5 100644 --- a/infer/tests/build_systems/utf8_in_procname/Makefile +++ b/infer/tests/build_systems/utf8_in_procname/Makefile @@ -17,7 +17,7 @@ SOURCES = ../codetoanalyze/make/utf8_in_function_names.c include $(TESTS_DIR)/clang.make -infer-out/report.json: $(CLANG_DEPS) $(INFERTRACEBUGS_BIN) $(SOURCES) $(HEADERS) $(MAKEFILE_LIST) +infer-out/report.json: $(CLANG_DEPS) $(SOURCES) $(HEADERS) $(MAKEFILE_LIST) # set non-utf8-supporting locale $(QUIET)LC_ALL=C; \ $(call silent_on_success,Testing Infer is immune to UTF-8 in procnames,\ @@ -25,9 +25,9 @@ infer-out/report.json: $(CLANG_DEPS) $(INFERTRACEBUGS_BIN) $(SOURCES) $(HEADERS) $(QUIET)$(call check_no_duplicates,infer-out/duplicates.txt) # make sure inferTraceBugs is immune to UTF-8 $(QUIET)$(call silent_on_success,Testing inferTraceBugs is immune to UTF-8 in procnames,\ - $(INFERTRACEBUGS_BIN) --max-level max --select 0) + $(INFER_BIN) explore --select 0) $(QUIET)$(call silent_on_success,Testing inferTraceBugs --html is immune to UTF-8 in procnames,\ - $(INFERTRACEBUGS_BIN) --html) + $(INFER_BIN) explore --html) $(QUIET)[ -f infer-out/report.html/index.html ] # run again to check that infer manages to delete the results directory $(QUIET)LC_ALL=C; \