sledge/cli/sledge_cli.ml (311 lines of code) (raw):
(*
* 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.
*)
(** SLEdge command line interface *)
module Command = Core.Command
open Command.Let_syntax
type 'a param = 'a Command.Param.t
(* reverse application in the Command.Param applicative *)
let ( |*> ) : 'a param -> ('a -> 'b) param -> 'b param =
fun x f -> x |> Command.Param.apply f
(* function composition in the Command.Param applicative *)
let ( >*> ) : ('a -> 'b) param -> ('b -> 'c) param -> ('a -> 'c) param =
fun f' g' -> Command.Param.both f' g' >>| fun (f, g) -> f >> g
(* define a command, with trace flag, and with action wrapped in
reporting *)
let command ~summary ?readme param =
let trace =
let%map_open config =
flag "trace" ~doc:"<spec> enable debug tracing"
(optional_with_default Trace.none
(Arg_type.create (fun s -> Trace.parse s |> Result.get_ok)) )
and colors = flag "colors" no_arg ~doc:"enable printing in colors"
and margin =
flag "margin" ~doc:"<cols> wrap debug tracing at <cols> columns"
(optional int)
and report =
flag "report" (optional string)
~doc:
"<file> write report sexp to <file>, or to standard error if \
\"-\""
and append_report =
flag "append-report" no_arg ~doc:"append to report file"
in
Trace.init ~colors ?margin ~config () ;
Option.iter ~f:(Report.init ~append:append_report) report
in
Llair.Loc.root := Some (Unix.realpath (Sys.getcwd ())) ;
let flush main () = Fun.protect main ~finally:Trace.flush in
let report main () =
try main () |> Report.status
with exn ->
let bt = Printexc.get_raw_backtrace () in
let rec status_of_exn = function
| Invariant.Violation (exn, _, _) | Replay (exn, _) ->
status_of_exn exn
| Frontend.Invalid_llvm msg -> Report.InvalidInput msg
| Unimplemented msg -> Report.Unimplemented msg
| Assert_failure _ as exn ->
Report.InternalError (Sexp.to_string_hum (sexp_of_exn exn))
| Failure msg -> Report.InternalError msg
| Stop.Stop -> Report.safe_or_unsafe ()
| exn -> Report.UnknownError (Printexc.to_string exn)
in
Report.status (status_of_exn exn) ;
Printexc.raise_with_backtrace exn bt
in
Command.basic ~summary ?readme (trace *> param >>| flush >>| report)
let marshal program file =
Out_channel.with_file file ~f:(fun oc -> Marshal.to_channel oc program [])
let unmarshal file () =
In_channel.with_file
~f:(fun ic : Llair.program -> Marshal.from_channel ic)
file
let entry_points = Config.find_list "entry-points"
let used_globals pgm entry_points preanalyze =
let module UG = Domain_used_globals in
if preanalyze then
let module Config = struct
let loop_bound = 1
let switch_bound = 0
let function_summaries = true
let entry_points = entry_points
let globals = UG.Declared Llair.Global.Set.empty
end in
let module Analysis = Control.Make (Config) (UG) (Control.PriorityQueue)
in
let summary_table = Analysis.compute_summaries pgm in
UG.Per_function
(Llair.Function.Map.map summary_table ~f:Llair.Global.Set.union_list)
else
UG.Declared
(Llair.Global.Set.of_iter
(Iter.map ~f:(fun g -> g.name) (IArray.to_iter pgm.globals)) )
let analyze =
let%map_open loop_bound =
flag "loop-bound" ~aliases:["bound"]
(optional_with_default 1 int)
~doc:
"<int> limit execution exploration to <int> loop iterations, a \
negative bound is never hit and leads to unbounded exploration"
and switch_bound =
flag "switch-bound" ~aliases:["yield"]
(optional_with_default (-1) int)
~doc:
"<int> limit execution exploration to <int> context switches, a \
negative bound is never hit and leads to unbounded exploration"
and cct_schedule_points =
flag "cct-schedule-points" no_arg
~doc:"context switch only at cct_point calls"
and function_summaries =
flag "function-summaries" no_arg
~doc:"use function summaries (in development)"
and preanalyze_globals =
flag "preanalyze-globals" no_arg
~doc:"pre-analyze global variables used by each function"
and domain =
flag "domain"
(optional_with_default `sh
(Arg_type.of_alist_exn
[ ("sh", `sh)
; ("globals", `globals)
; ("itv", `itv)
; ("unit", `unit) ] ) )
~doc:
"<string> select abstract domain; must be one of \"sh\" (default, \
symbolic heap domain), \"globals\" (used-globals domain), or \
\"unit\" (unit domain)"
and sample = flag "sample" no_arg ~doc:" randomly sample execution paths"
and seed =
flag "seed" (optional int)
~doc:"<int> specify random number generator seed"
and normalize_states =
flag "normalize-states" no_arg
~doc:"normalize states during symbolic execution"
and no_simplify_states =
flag "no-simplify-states" no_arg
~doc:"do not simplify states during symbolic execution"
and stats =
flag "stats" no_arg ~doc:"output performance statistics to stderr"
and dump_query =
flag "dump-query" (optional int)
~doc:"<int> dump solver query <int> and halt"
and dump_simplify =
flag "dump-simplify" (optional int)
~doc:"<int> dump simplify query <int> and halt"
in
fun program () ->
Timer.enabled := stats ;
let pgm = program () in
let globals = used_globals pgm entry_points preanalyze_globals in
let module Config = struct
let loop_bound = if loop_bound < 0 then Int.max_int else loop_bound
let switch_bound =
if switch_bound < 0 then Int.max_int else switch_bound
let function_summaries = function_summaries
let entry_points = entry_points
let globals = globals
end in
let dom : (module Domain_intf.Domain) =
match domain with
| `sh -> (module Domain_relation.Make (Domain_sh))
| `globals -> (module Domain_used_globals)
| `itv -> (module Domain_itv)
| `unit -> (module Domain_unit)
in
let module Domain = (val dom) in
let queue : (module Control.Queue) =
if sample then (module Control.RandomQueue)
else (module Control.PriorityQueue)
in
let module Queue = (val queue) in
let module Analysis = Control.Make (Config) (Domain) (Queue) in
(match seed with None -> Random.self_init () | Some n -> Random.init n) ;
Llair.cct_schedule_points := cct_schedule_points ;
Sh.do_normalize := normalize_states ;
Domain_sh.simplify_states := not no_simplify_states ;
Option.iter dump_query ~f:(fun n -> Solver.dump_query := n) ;
Option.iter dump_simplify ~f:(fun n -> Sh.dump_simplify := n) ;
at_exit (fun () -> Report.coverage pgm) ;
Analysis.exec_pgm pgm ;
Report.safe_or_unsafe ()
let analyze_cmd =
let summary = "analyze LLAIR code" in
let readme () =
"The <input> file must be binary LLAIR, such as produced by `sledge \
translate`."
in
let param =
Command.Param.(anon ("<input>" %: string) >>| unmarshal |*> analyze)
in
command ~summary ~readme param
let disassemble =
let%map_open llair_output =
flag "llair-output" (optional string)
~doc:
"<file> write generated textual LLAIR to <file>, or to standard \
output if omitted"
in
fun program () ->
let pgm = program () in
( match llair_output with
| None -> Format.printf "%a@." Llair.Program.pp pgm
| Some file ->
Out_channel.with_file file ~f:(fun oc ->
let fs = Format.formatter_of_out_channel oc in
Format.fprintf fs "%a@." Llair.Program.pp pgm ) ) ;
Report.Ok
let disassemble_cmd =
let summary = "print LLAIR code in textual form" in
let readme () =
"The <input> file must be LLAIR code, as produced by `sledge llvm \
translate`."
in
let param =
Command.Param.(anon ("<input>" %: string) >>| unmarshal |*> disassemble)
in
command ~summary ~readme param
let translate =
let%map_open dump_bitcode =
flag "dump-bitcode" (optional string)
~doc:"<file> write transformed LLVM bitcode to <file>"
and output =
flag "output" (optional string)
~doc:"<file> write generated binary LLAIR to <file>"
and opt_level, size_level =
choose_one
[ flag "O0" (no_arg_some (0, 0)) ~doc:" optimization level 0"
; flag "O1" (no_arg_some (1, 0)) ~doc:" optimization level 1"
; flag "O2" (no_arg_some (2, 0)) ~doc:" optimization level 2"
; flag "O3" (no_arg_some (3, 0)) ~doc:" optimization level 3"
; flag "Os"
(no_arg_some (2, 1))
~doc:" like -O2 with extra optimizations for size"
; flag "Oz"
(no_arg_some (2, 2))
~doc:" like -Os but reduces code size further (default)" ]
~if_nothing_chosen:(Default_to (2, 2))
and no_internalize =
flag "no-internalize" no_arg
~doc:
"do not internalize all functions except the entry points \
specified in the config file"
in
fun bitcode_input () ->
let program =
Frontend.translate ~internalize:(not no_internalize) ~opt_level
~size_level bitcode_input ?dump_bitcode
in
Option.iter ~f:(marshal program) output ;
program
let llvm_grp =
let translate_input =
Command.Param.(anon ("<input>" %: string) |*> translate)
in
let translate_cmd =
let summary = "translate LLVM bitcode to LLAIR" in
let readme () =
"Translate LLVM bitcode to LLAIR. The <input> file must contain LLVM \
bitcode in either binary (.bc) or textual (.ll) form."
in
let param =
translate_input >*> Command.Param.return (fun _ -> Report.Ok)
in
command ~summary ~readme param
in
let disassemble_cmd =
let summary =
"translate LLVM bitcode to LLAIR and print in textual form"
in
let readme () = "The <input> file must be LLVM bitcode." in
let param = translate_input |*> disassemble in
command ~summary ~readme param
in
let analyze_cmd =
let summary = "analyze LLVM bitcode" in
let readme () =
"Analyze LLVM bitcode. This is a convenience wrapper for the \
sequence `sledge llvm translate`; `sledge analyze`."
in
let param = translate_input |*> analyze in
command ~summary ~readme param
in
let summary = "integration with LLVM" in
Command.group ~summary ~preserve_subcommand_order:()
[ ("analyze", analyze_cmd)
; ("translate", translate_cmd)
; ("disassemble", disassemble_cmd) ]
let smt_cmd =
let summary = "process SMT-LIB benchmarks" in
let readme () =
"The <input> file is interpreted as an SMT-LIB 2 benchmark."
in
let param =
let%map_open input = anon ("<input>" %: string) in
fun () -> Smtlib.process input
in
command ~summary ~readme param
let summary = "SLEdge static analyzer"
let readme () =
"The [-trace <spec>] argument of each subcommand enables debug tracing \
according to <spec>, which is a sequence of module and function names \
separated by + or -. For example, M-M.f enables all tracing in the M \
module except the M.f function. The <spec> value * enables all debug \
tracing."
;;
Memtrace.trace_if_requested ()
;;
if Version.debug then Printexc.record_backtrace true
;;
Stdlib.Sys.catch_break true
;;
Command.run ~version:Version.version ~build_info:Version.build_info
(Command.group ~summary ~readme ~preserve_subcommand_order:()
[ ("buck", Sledge_buck.main ~command)
; ("llvm", llvm_grp)
; ("analyze", analyze_cmd)
; ("disassemble", disassemble_cmd)
; ("smt", smt_cmd) ] )