From 3fb5427b0c2b3deb9abfac893bc3e2ec4e87e56a Mon Sep 17 00:00:00 2001 From: Jules Villard Date: Mon, 30 Mar 2020 05:27:07 -0700 Subject: [PATCH] [python] partial rewrite of inferTraceBugs in OCaml Summary: HTML output is missing but the rest is there. Reviewed By: skcho Differential Revision: D20649221 fbshipit-source-id: 6bc91161a --- infer/src/infer.ml | 42 ++++++++----- infer/src/integration/TextReport.ml | 12 ++-- infer/src/integration/TextReport.mli | 5 ++ infer/src/integration/TraceBugs.ml | 93 ++++++++++++++++++++++++++++ infer/src/integration/TraceBugs.mli | 16 +++++ 5 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 infer/src/integration/TraceBugs.ml create mode 100644 infer/src/integration/TraceBugs.mli diff --git a/infer/src/infer.ml b/infer/src/infer.ml index bd2982de1..316e94f2b 100644 --- a/infer/src/infer.ml +++ b/infer/src/infer.ml @@ -207,23 +207,31 @@ let () = DotCfg.emit_frontend_cfg source_file cfgs ) ; L.result "CFGs written in %s/*/%s@." Config.captured_dir Config.dotty_frontend_output ) | false, false -> - 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) + (* explore bug traces *) + if Config.html then ( + 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) ) + else + TraceBugs.explore ~selector_limit:None + ~report_json:Config.(results_dir ^/ report_json) + ~report_txt:Config.(results_dir ^/ report_txt) + ~selected:Config.select ~show_source_context:Config.source_preview + ~max_nested_level:Config.max_nesting | true, true -> L.user_error "Options --procedures and --source-files cannot be used together.@\n" ) ) ; (* to make sure the exitcode=0 case is logged, explicitly invoke exit *) diff --git a/infer/src/integration/TextReport.ml b/infer/src/integration/TextReport.ml index 05ea8aee4..ddb228fcc 100644 --- a/infer/src/integration/TextReport.ml +++ b/infer/src/integration/TextReport.ml @@ -57,8 +57,8 @@ let pp_jsonbug fmt {Jsonbug_t.file; severity; line; bug_type; qualifier; _} = F.fprintf fmt "%s:%d: %s: %s@\n %s" file line (String.lowercase severity) bug_type qualifier -let pp_source_context fmt {Jsonbug_t.file= source_name; lnum= report_line; cnum= report_col; enum= _} - = +let pp_source_context ~indent fmt + {Jsonbug_t.file= source_name; lnum= report_line; cnum= report_col; enum= _} = let source_name = if Filename.is_absolute source_name then source_name else Config.project_root ^/ source_name in @@ -78,7 +78,7 @@ let pp_source_context fmt {Jsonbug_t.file= source_name; lnum= report_line; cnum= ~f:(fun line_number line -> if start_line <= line_number && line_number <= end_line then ( (* we are inside the context to print *) - F.fprintf fmt " %*d. " n_length line_number ; + F.fprintf fmt "%t%*d. " (pp_n_spaces indent) n_length line_number ; if report_col < 0 then (* no column number, print caret next to the line of the report *) if Int.equal line_number report_line then F.pp_print_string fmt "> " @@ -86,7 +86,7 @@ let pp_source_context fmt {Jsonbug_t.file= source_name; lnum= report_line; cnum= F.pp_print_string fmt line ; F.pp_print_newline fmt () ; if Int.equal line_number report_line && report_col >= 0 then ( - pp_n_spaces (2 + n_length + 1 + report_col) fmt ; + pp_n_spaces (indent + n_length + 1 + report_col) fmt ; F.pp_print_char fmt '^' ; F.pp_print_newline fmt () ) ) ; if line_number < end_line then Continue (line_number + 1) else Stop () ) ) @@ -96,7 +96,7 @@ let create_from_json ~quiet ~console_limit ~report_txt ~report_json = (* TOOD: possible optimisation: stream reading report.json to process each issue one by one *) let report = Atdgen_runtime.Util.Json.from_file Jsonbug_j.read_report report_json in let one_issue_to_report_txt fmt (jsonbug : Jsonbug_t.jsonbug) = - F.fprintf fmt "%a@\n%a@\n" pp_jsonbug jsonbug pp_source_context + F.fprintf fmt "%a@\n%a@\n" pp_jsonbug jsonbug (pp_source_context ~indent:2) {Jsonbug_t.file= jsonbug.file; lnum= jsonbug.line; cnum= jsonbug.column; enum= -1} in let one_issue_to_console ~console_limit i (jsonbug : Jsonbug_t.jsonbug) = @@ -116,7 +116,7 @@ let create_from_json ~quiet ~console_limit ~report_txt ~report_json = F.printf "%!" ; ANSITerminal.print_string style (F.asprintf "%a" pp_jsonbug jsonbug) ; F.printf "%!" ; - F.printf "@\n%a@\n" pp_source_context + F.printf "@\n%a@\n" (pp_source_context ~indent:2) {Jsonbug_t.file= jsonbug.file; lnum= jsonbug.line; cnum= jsonbug.column; enum= -1} in Utils.with_file_out report_txt ~f:(fun report_txt_out -> diff --git a/infer/src/integration/TextReport.mli b/infer/src/integration/TextReport.mli index b1b0b3347..09aff3fa1 100644 --- a/infer/src/integration/TextReport.mli +++ b/infer/src/integration/TextReport.mli @@ -5,8 +5,13 @@ * LICENSE file in the root directory of this source tree. *) open! IStd +module F = Format val create_from_json : quiet:bool -> console_limit:int option -> report_txt:string -> report_json:string -> unit (** Read [report_json] and produce a textual output in [report_txt]. If [not quiet] then display at most [console_limit] issues on stdout. If [console_limit] is [None] then display all the issues. *) + +val pp_jsonbug : F.formatter -> Jsonbug_t.jsonbug -> unit + +val pp_source_context : indent:int -> F.formatter -> Jsonbug_t.loc -> unit diff --git a/infer/src/integration/TraceBugs.ml b/infer/src/integration/TraceBugs.ml new file mode 100644 index 000000000..921aed384 --- /dev/null +++ b/infer/src/integration/TraceBugs.ml @@ -0,0 +1,93 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd +module F = Format +module L = Logging + +[@@@warning "+9"] + +let is_past_limit limit = + match limit with None -> fun _ -> false | Some limit -> fun n -> n >= limit + + +let pp_trace_item ~show_source_context fmt + Jsonbug_t.{level; filename; line_number; column_number; description} = + let pp_col_number fmt c = if c >= 0 then F.fprintf fmt ":%d" c in + F.fprintf fmt "%s:%d%a: %s@\n" filename line_number pp_col_number column_number description ; + if show_source_context then + TextReport.pp_source_context ~indent:(2 * level) fmt + {Jsonbug_t.file= filename; lnum= line_number; cnum= column_number; enum= -1} + + +let show_issue_with_trace ~show_source_context ~max_nested_level + (n_issue, (issue : Jsonbug_t.jsonbug)) = + L.result "#%d@\n%a@\n" n_issue TextReport.pp_jsonbug issue ; + if List.is_empty issue.bug_trace then L.result "@\nEmpty trace@\n%!" + else + List.iter issue.bug_trace ~f:(fun trace_item -> + (* subtract 1 to get inclusive limits on the nesting level *) + if not (is_past_limit max_nested_level (trace_item.Jsonbug_t.level - 1)) then + L.result "@\n%a" (pp_trace_item ~show_source_context) trace_item ) + + +let user_select_issue ~selector_limit report = + List.fold_until report + ~finish:(fun _ -> ()) + ~init:0 + ~f:(fun n issue -> + if is_past_limit selector_limit n then Stop () + else ( + L.result "#%d@\n%a@\n" n TextReport.pp_jsonbug issue ; + Continue (n + 1) ) ) ; + let rec ask_until_valid_input max_report = + L.result "@\nSelect report to display (0-%d) (default: 0): %!" max_report ; + let input = In_channel.input_line_exn In_channel.stdin in + if String.is_empty input then 0 + else + match int_of_string_opt input with + | Some n when n >= 0 && n <= max_report -> + n + | Some n -> + L.progress "Error: %d is not between 0 and %d, try again.@\n%!" n max_report ; + ask_until_valid_input max_report + | None -> + L.progress "Error: please input a number, not '%s'.@\n%!" input ; + ask_until_valid_input max_report + in + let n = ask_until_valid_input (List.length report - 1) in + (n, List.nth_exn report n) + + +let explore ~selector_limit ~report_txt:_ ~report_json ~show_source_context ~selected + ~max_nested_level = + let report = Atdgen_runtime.Util.Json.from_file Jsonbug_j.read_report report_json in + let issue_to_display = + match (selected, report) with + | Some n, _ -> ( + (* an issue number has been pre-selected, use that *) + match List.nth report n with + | None -> + L.die UserError "Cannot select issues #%d: only %d issues in '%s'" n (List.length report) + report_json + | Some issue -> + Some (n, issue) ) + | None, [] -> + (* empty report, can't print anything *) + L.progress "No issues found in '%s', exiting.@\n" report_json ; + None + | None, [issue] -> + (* single-issue report: no need to prompt the user to select which issue to display *) + L.progress "Auto-selecting the only issue in '%s'@\n%!" report_json ; + Some (0, issue) + | None, _ :: _ :: _ -> + (* user prompt *) + Some (user_select_issue ~selector_limit report) + in + Option.iter issue_to_display + ~f: + ( L.result "@\n" ; + show_issue_with_trace ~show_source_context ~max_nested_level ) diff --git a/infer/src/integration/TraceBugs.mli b/infer/src/integration/TraceBugs.mli new file mode 100644 index 000000000..f12bd2764 --- /dev/null +++ b/infer/src/integration/TraceBugs.mli @@ -0,0 +1,16 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd + +val explore : + selector_limit:int option + -> report_txt:string + -> report_json:string + -> show_source_context:bool + -> selected:int option + -> max_nested_level:int option + -> unit