-
Make it into a new strategy module that should prepare the task before sending it to the provers.
Make it into a new strategy module that should prepare the task before sending it to the provers.
main.ml 10.07 KiB
(**************************************************************************)
(* *)
(* This file is part of CAISAR. *)
(* *)
(* Copyright (C) 2022 *)
(* CEA (Commissariat à l'énergie atomique et aux énergies *)
(* alternatives) *)
(* *)
(* You can redistribute it and/or modify it under the terms of the GNU *)
(* Lesser General Public License as published by the Free Software *)
(* Foundation, version 2.1. *)
(* *)
(* It is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU Lesser General Public License for more details. *)
(* *)
(* See the GNU Lesser General Public License version 2.1 *)
(* for more details (enclosed in the file licenses/LGPLv2.1). *)
(* *)
(**************************************************************************)
open Base
open Cmdliner
let caisar = "caisar"
let () =
Nn2smt.init ();
Vars_on_lhs.init ()
let () =
Pyrat.init ();
Marabou.init ();
Vnnlib.init ()
(* -- Logs. *)
let pp_header =
let x =
match Array.length Caml.Sys.argv with
| 0 -> Caml.(Filename.basename Sys.executable_name)
| _ -> Caml.(Filename.basename Sys.argv.(0))
in
let pp_h ppf style h = Fmt.pf ppf "[%s][%a] " x Fmt.(styled style string) h in
fun ppf (l, h) ->
let open Logs_fmt in
match l with
| Logs.App -> Fmt.pf ppf "[%a] " Fmt.(styled app_style string) x
| Logs.Error ->
pp_h ppf err_style (match h with None -> "ERROR" | Some h -> h)
| Logs.Warning ->
pp_h ppf warn_style (match h with None -> "WARNING" | Some h -> h)
| Logs.Info ->
pp_h ppf info_style (match h with None -> "INFO" | Some h -> h)
| Logs.Debug ->
pp_h ppf debug_style (match h with None -> "DEBUG" | Some h -> h)
let setup_logs =
let setup_log level =
Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ();
Logs.set_level level;
Logs.set_reporter (Logs_fmt.reporter ~pp_header ())
in
Term.(const setup_log $ Logs_cli.level ())
let log_level_is_debug () =
match Logs.level () with Some Debug -> true | _ -> false
(* -- Commands. *)
let config detect () =
if detect
then (
Logs.debug (fun m -> m "Automatic detection");
let config =
let debug = log_level_is_debug () in
Autodetect.autodetection ~debug ()
in
let open Why3 in
let provers = Whyconf.get_provers config in
if not (Whyconf.Mprover.is_empty provers)
then
Logs.app (fun m ->
m "@[<v>%a@]"
(Pp.print_iter2 Whyconf.Mprover.iter Pp.newline Pp.nothing
Whyconf.print_prover Pp.nothing)
provers))
let memlimit_of_string s =
let s = String.strip s in
let value =
let re = Re__Pcre.regexp "[0-9]+" in
Re__Core.matches re s
in
let unit =
let re = Re__Pcre.regexp "MB?|GB?|TB?" in
Re__Core.matches re s
in
match (value, unit) with
| [ v ], ([] | [ "M" ] | [ "MB" ]) -> Int.of_string v
| [ v ], ([ "G" ] | [ "GB" ]) -> Int.of_string v * 1000
| [ v ], ([ "T" ] | [ "TB" ]) -> Int.of_string v * 1000000
| _ -> invalid_arg "Unrecognized memory limit"
let timelimit_of_string s =
let s = String.strip s in
let value =
let re = Re__Pcre.regexp "[0-9]+" in
Re__Core.matches re s
in
let unit =
let re = Re__Pcre.regexp "[a-z]" in
Re__Core.matches re s
in
match (value, unit) with
| [ v ], ([] | [ "s" ]) -> Int.of_string v
| [ v ], [ "m" ] -> Int.of_string v * 60
| [ v ], [ "h" ] -> Int.of_string v * 3600
| _ -> invalid_arg "Unrecognized time limit"
let verify ?format ~loadpath ?memlimit ?timelimit prover ?dataset_csv files =
let debug = log_level_is_debug () in
let memlimit = Option.map memlimit ~f:memlimit_of_string in
let timelimit = Option.map timelimit ~f:timelimit_of_string in
List.iter
~f:
(Verification.verify ~debug ?format ~loadpath ?memlimit ?timelimit prover
?dataset_csv)
files
let verify_json ?memlimit ?timelimit file =
let jc = Result.ok_exn (Verification.JSON.of_string file) in
let prover = jc.Verification.JSON.prover in
let dataset_csv = jc.Verification.JSON.property.dataset in
let file = Result.ok_or_failwith (Verification.File.of_json_config jc) in
verify ~loadpath:[] ?memlimit ?timelimit prover ~dataset_csv [ file ]
let exec_cmd cmdname cmd =
Logs.debug (fun m -> m "Command `%s'" cmdname);
cmd ()
(* -- Command line. *)
let config_cmd =
let cmdname = "config" in
let info =
let doc = Fmt.str "%s configuration." caisar in
let exits = Cmd.Exit.defaults in
let man =
[
`S Manpage.s_description;
`P (Fmt.str "Handle the configuration of %s." caisar);
]
in
Cmd.info cmdname ~sdocs:Manpage.s_common_options ~exits ~doc ~man
in
let term =
let detect =
let doc = "Detect solvers in \\$PATH." in
Arg.(value & flag & info [ "d"; "detect" ] ~doc)
in
Term.(
ret
(const (fun detect _ ->
if not detect
then `Help (`Pager, Some cmdname)
else
(* TODO: Do not only check for [detect], and enable it by default,
as soon as other options are available. *)
`Ok (exec_cmd cmdname (fun () -> config true ())))
$ detect $ setup_logs))
in
Cmd.v info term
let memlimit =
let doc =
"Memory limit. $(docv) is intended in megabytes (MB) by default, but \
gigabytes (GB) and terabytes (TB) may also be specified instead."
in
Arg.(
value & opt (some string) None & info [ "m"; "memlimit" ] ~doc ~docv:"MEM")
let timelimit =
let doc =
"Time limit. $(docv) is intended in seconds (s) by default, but minutes \
(m) and hours (h) may also be specified instead."
in
Arg.(
value & opt (some string) None & info [ "t"; "timelimit" ] ~doc ~docv:"TIME")
let verify_cmd =
let cmdname = "verify" in
let info =
let doc = "Property verification using external provers." in
Cmd.info cmdname ~sdocs:Manpage.s_common_options ~exits:Cmd.Exit.defaults
~doc
~man:[ `S Manpage.s_description; `P doc ]
in
let loadpath =
let doc = "Additional loadpath." in
Arg.(value & opt_all string [ "." ] & info [ "L"; "loadpath" ] ~doc)
in
let format =
let doc = "File format." in
Arg.(value & opt (some string) None & info [ "format" ] ~doc)
in
let term =
let files =
let doc = "File to verify." in
let file_or_stdin = Arg.conv' Verification.File.(of_string, pretty) in
Arg.(non_empty & pos_all file_or_stdin [] & info [] ~doc ~docv:"FILE")
in
let prover =
let all_provers = Prover.list_available () in
let doc =
Fmt.str
"Prover to use. Support is provided for the following provers: %a."
(Fmt.list ~sep:Fmt.comma Fmt.string)
(List.map ~f:Prover.to_string all_provers)
in
let provers =
Arg.enum (List.map ~f:(fun p -> (Prover.to_string p, p)) all_provers)
in
Arg.(required & opt (some provers) None & info [ "p"; "prover" ] ~doc)
in
let dataset_csv =
let doc = "Dataset in CSV format." in
Arg.(value & opt (some file) None & info [ "dataset-csv" ] ~doc)
in
Term.(
const
(fun format loadpath memlimit timelimit prover dataset_csv files _ ->
exec_cmd cmdname (fun () ->
verify ?format ~loadpath ?memlimit ?timelimit prover ?dataset_csv
files))
$ format $ loadpath $ memlimit $ timelimit $ prover $ dataset_csv $ files
$ setup_logs)
in
Cmd.v info term
let verify_json_cmd =
let cmdname = "verify-json" in
let info =
let doc =
"Property verification using external provers via json configuration \
file."
in
Cmd.info cmdname ~sdocs:Manpage.s_common_options ~exits:Cmd.Exit.defaults
~doc
~man:[ `S Manpage.s_description; `P doc ]
in
let term =
let json =
let doc = "JSON file." in
Arg.(
required & pos ~rev:true 0 (some file) None & info [] ~doc ~docv:"FILE")
in
Term.(
const (fun memlimit timelimit json _ ->
exec_cmd cmdname (fun () -> verify_json ?memlimit ?timelimit json))
$ memlimit $ timelimit $ json $ setup_logs)
in
Cmd.v info term
let default_info =
let doc =
"A platform for characterizing the safety and robustness of artificial \
intelligence based software."
in
let sdocs = Manpage.s_common_options in
let man =
[
`S Manpage.s_common_options;
`P "These options are common to all commands.";
`S "MORE HELP";
`P "Use `$(mname) $(i,COMMAND) --help' for help on a single command.";
`S Manpage.s_bugs;
`P "Email bug reports to <...>";
]
in
let version = "0.1" in
let exits = Cmd.Exit.defaults in
Cmd.info caisar ~version ~doc ~sdocs ~exits ~man
let default_cmd = Term.(ret (const (fun _ -> `Help (`Pager, None)) $ const ()))
let () =
(* We register custom printers for a selection of exceptions otherwise Why3
will print the related messages as "anomaly: <exception with message>". *)
Why3.Exn_printer.register (fun fmt exn ->
match exn with
| Invalid_argument msg -> Fmt.pf fmt "Invalid argument: %s" msg
| Failure msg -> Fmt.pf fmt "Failure: %s" msg
| _ -> raise exn)
let () =
try
Cmd.group ~default:default_cmd default_info
[ config_cmd; verify_cmd; verify_json_cmd ]
|> Cmd.eval ~catch:false |> Caml.exit
with exn when not (log_level_is_debug ()) ->
Logs.err (fun m -> m "@[%a@]" Why3.Exn_printer.exn_printer exn)