Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YAML witness output #744

Merged
merged 19 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
(sha (>= 1.12))
cpu
arg-complete
yaml
uuidm
(conf-gmp (>= 3)) ; only needed transitively, but they don't have lower bound, which is needed on MacOS
(conf-ruby :with-test)
(benchmark :with-test) ; TODO: make this optional somehow, (optional) on bench executable doesn't work
Expand Down
2 changes: 2 additions & 0 deletions goblint.opam
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ depends: [
"sha" {>= "1.12"}
"cpu"
"arg-complete"
"yaml"
"uuidm"
"conf-gmp" {>= "3"}
"conf-ruby" {with-test}
"benchmark" {with-test}
Expand Down
10 changes: 9 additions & 1 deletion goblint.opam.locked
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ depends: [
"bigarray-compat" {= "1.1.0"}
"bigstringaf" {= "0.8.0"}
"biniou" {= "1.2.1"}
"bos" {= "0.2.1"}
"camlidl" {= "1.09"}
"cmdliner" {= "1.1.1" & with-doc}
"conf-autoconf" {= "0.1"}
Expand All @@ -43,16 +44,20 @@ depends: [
"cppo" {= "1.6.8"}
"cpu" {= "2.0.0"}
"csexp" {= "1.5.1"}
"ctypes" {= "0.20.1"}
"dune" {= "3.0.3"}
"dune-private-libs" {= "3.0.3"}
"dune-site" {= "3.0.3"}
"dyn" {= "3.0.3"}
"dune-configurator" {= "3.0.3"}
"easy-format" {= "1.3.2"}
"fmt" {= "0.9.0" & with-doc}
"fmt" {= "0.9.0"}
"fpath" {= "0.7.3"}
"goblint-cil" {= "1.8.2"}
"integers" {= "0.7.0"}
"json-data-encoding" {= "0.11"}
"jsonrpc" {= "1.10.3"}
"logs" {= "0.7.0"}
"mlgmpidl" {= "1.2.14"}
"num" {= "1.4"}
"ocaml" {= "4.14.0"}
Expand Down Expand Up @@ -80,6 +85,7 @@ depends: [
"qcheck-ounit" {= "0.18.1" & with-test}
"re" {= "1.10.3" & with-doc}
"result" {= "1.5"}
"rresult" {= "0.7.0"}
"seq" {= "base" & with-test}
"sexplib0" {= "v0.14.0"}
"sha" {= "1.15.2"}
Expand All @@ -89,7 +95,9 @@ depends: [
"topkg" {= "1.0.5"}
"tyxml" {= "4.5.0" & with-doc}
"uri" {= "4.2.0"}
"uuidm" {= "0.9.8"}
"uutf" {= "1.0.3" & with-doc}
"yaml" {= "3.1.0"}
"yojson" {= "1.7.0"}
"zarith" {= "1.12"}
]
Expand Down
2 changes: 1 addition & 1 deletion src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
(public_name goblint.lib)
(wrapped false)
(modules :standard \ goblint mainarinc mainspec privPrecCompare apronPrecCompare messagesCompare)
(libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath
(libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath yaml yaml.unix uuidm
; Conditionally compile based on whether apron optional dependency is installed or not.
; Alternative dependencies seem like the only way to optionally depend on optional dependencies.
; See: https://dune.readthedocs.io/en/stable/concepts.html#alternative-dependencies.
Expand Down
4 changes: 2 additions & 2 deletions src/framework/analyses.ml
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ struct
Messages.xml_file_name := fn;
BatPrintf.printf "Writing xml to temp. file: %s\n%!" fn;
BatPrintf.fprintf f "<run>";
BatPrintf.fprintf f "<parameters>%a</parameters>" (BatArray.print ~first:"" ~last:"" ~sep:" " BatString.print) BatSys.argv;
BatPrintf.fprintf f "<parameters>%s</parameters>" Goblintutil.command_line;
BatPrintf.fprintf f "<statistics>";
(* FIXME: This is a super ridiculous hack we needed because BatIO has no way to get the raw channel CIL expects here. *)
let name, chn = Filename.open_temp_file "stat" "goblint" in
Expand Down Expand Up @@ -251,7 +251,7 @@ struct
let p_file f x = fprintf f "{\n \"name\": \"%s\",\n \"path\": \"%s\",\n \"functions\": %a\n}" (Filename.basename x) x (p_list p_fun) (SH.find_all file2funs x) in
let write_file f fn =
printf "Writing json to temp. file: %s\n%!" fn;
fprintf f "{\n \"parameters\": \"%a\",\n " (BatArray.print ~first:"" ~last:"" ~sep:" " BatString.print) BatSys.argv;
fprintf f "{\n \"parameters\": \"%s\",\n " Goblintutil.command_line;
fprintf f "\"files\": %a,\n " (p_enum p_file) (SH.keys file2funs);
fprintf f "\"results\": [\n %a\n]\n" printJson (Lazy.force table);
(*gtfxml f gtable;*)
Expand Down
7 changes: 6 additions & 1 deletion src/framework/control.ml
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ struct
GobConfig.write_file config;
let module Meta = struct
type t = { command : string; version: string; timestamp : float; localtime : string } [@@deriving to_yojson]
let json = to_yojson { command = GU.command; version = Version.goblint; timestamp = Unix.time (); localtime = localtime () }
let json = to_yojson { command = GU.command_line; version = Version.goblint; timestamp = Unix.time (); localtime = localtime () }
end
in
(* Yojson.Safe.to_file meta Meta.json; *)
Expand Down Expand Up @@ -678,6 +678,11 @@ struct
if get_bool "ana.sv-comp.enabled" then
WResult.write lh gh entrystates;

if get_bool "witness.yaml.enabled" then (
let module YWitness = YamlWitness.Make (struct let file = file end) (Cfg) (Spec) (EQSys) (LHT) (GHT) in
YWitness.write lh gh
);

let marshal = Spec.finalize () in
(* copied from solve_and_postprocess *)
let gobview = get_bool "gobview" in
Expand Down
2 changes: 1 addition & 1 deletion src/goblint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let main () =
handle_flags ();
if get_bool "dbg.verbose" then (
print_endline (localtime ());
print_endline command;
print_endline Goblintutil.command_line;
);
let file = Fun.protect ~finally:GoblintDir.finalize preprocess_and_merge in
if get_bool "server.enabled" then Server.start file else (
Expand Down
52 changes: 15 additions & 37 deletions src/transform/evalAssert.ml
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
open Prelude
open Cil
open Formatcil
module ES = SetDomain.Make(Exp.Exp)

(** Instruments a program by inserting asserts either:
- After an assignment to a variable (unless trans.assert.full is activated) and
- After an assignment to a variable (unless witness.invariant.full is activated) and
- At join points about all local variables

OR

- Only after pthread_mutex_lock (trans.assert.only-at-locks), about all locals and globals
- Only after pthread_mutex_lock (witness.invariant.after-lock), about all locals and globals

Limitations without trans.assert.only-at locks:
Limitations without witness.invariant.after-lock:
- Currently only works for top-level variables (not inside an array, a struct, ...)
- Does not work for accesses through pointers
- At join points asserts all locals, but ideally should only assert ones that are
Expand All @@ -23,9 +22,6 @@ module ES = SetDomain.Make(Exp.Exp)
*)

module EvalAssert = struct
(* should asserts of conjuncts be one-by-one instead of one big assert? *)
let distinctAsserts = true

(* should asserts be surrounded by __VERIFIER_atomic_{begin,end}? *)
let surroundByAtomic = true

Expand All @@ -35,30 +31,12 @@ module EvalAssert = struct
let atomicEnd = makeVarinfo true "__VERIFIER_atomic_end" (TVoid [])


(* Turns an expression into alist of conjuncts, pulling out common conjuncts from top-level disjunctions *)
let rec pullOutCommonConjuncts e =
let rec to_conjunct_set = function
| BinOp(LAnd,e1,e2,_) -> ES.join (to_conjunct_set e1) (to_conjunct_set e2)
| e -> ES.singleton e
in
let combine_conjuncts es = ES.fold (fun e acc -> match acc with | None -> Some e | Some acce -> Some (BinOp(LAnd,acce,e,Cil.intType))) es None in
match e with
| BinOp(LOr, e1, e2,t) ->
let e1s = pullOutCommonConjuncts e1 in
let e2s = pullOutCommonConjuncts e2 in
let common = ES.inter e1s e2s in
let e1s' = ES.diff e1s e2s in
let e2s' = ES.diff e2s e1s in
(match combine_conjuncts e1s', combine_conjuncts e2s' with
| Some e1e, Some e2e -> ES.add (BinOp(LOr,e1e,e2e,Cil.intType)) common
| _ -> common (* if one of the disjuncts is empty, it is equivalent to true here *)
)
| e -> to_conjunct_set e

class visitor (ask:Cil.location -> Queries.ask) = object(self)
inherit nopCilVisitor
val full = GobConfig.get_bool "trans.assert.full"
val only_at_locks = GobConfig.get_bool "trans.assert.only-at-locks"
val full = GobConfig.get_bool "witness.invariant.full"
(* TODO: handle witness.invariant.loop-head *)
val emit_after_lock = GobConfig.get_bool "witness.invariant.after-lock"
val emit_other = GobConfig.get_bool "witness.invariant.other"

method! vstmt s =
let is_lock exp args =
Expand All @@ -78,7 +56,7 @@ module EvalAssert = struct
} in
match (ask loc).f (Queries.Invariant context) with
| `Lifted e ->
let es = if distinctAsserts then ES.elements (pullOutCommonConjuncts e) else [e] in
let es = WitnessUtil.InvariantExp.process_exp e in
let asserts = List.map (fun e -> cInstr ("%v:assert (%e:exp);") loc [("assert", Fv ass); ("exp", Fe e)]) es in
if surroundByAtomic then
let abegin = (cInstr ("%v:__VERIFIER_atomic_begin();") loc [("__VERIFIER_atomic_begin", Fv atomicBegin)]) in
Expand All @@ -94,13 +72,13 @@ module EvalAssert = struct
let unique_succ = s.succs <> [] && (List.hd s.succs).preds |> List.length < 2 in
let instrument i loc =
let instrument' lval =
let lval_arg = if full || only_at_locks then None else lval in
let lval_arg = if full then None else lval in
make_assert loc lval_arg
in
match i with
| Set (lval, _, _, _) when not only_at_locks -> instrument' (Some lval)
| Call (lval, _, _, _, _) when not only_at_locks -> instrument' lval
| Call (_, exp, args, _, _) when is_lock exp args -> instrument' None
| Call (_, exp, args, _, _) when emit_after_lock && is_lock exp args -> instrument' None
| Set (lval, _, _, _) when emit_other -> instrument' (Some lval)
| Call (lval, _, _, _, _) when emit_other -> instrument' lval
| _ -> []
in
let rec instrument_instructions = function
Expand Down Expand Up @@ -132,10 +110,10 @@ module EvalAssert = struct

let instrument_join s =
match s.preds with
| [p1; p2] when not only_at_locks ->
| [p1; p2] when emit_other ->
(* exactly two predecessors -> join point, assert locals if they changed *)
let join_loc = get_stmtLoc s.skind in
(* Possible enhancement: It would be nice to only assert locals here that were modified in either branch if trans.assert.full is false *)
(* Possible enhancement: It would be nice to only assert locals here that were modified in either branch if witness.invariant.full is false *)
let asserts = make_assert join_loc None in
self#queueInstr asserts; ()
| _ -> ()
Expand All @@ -161,7 +139,7 @@ module EvalAssert = struct
else
()
in
if not only_at_locks then (add_asserts b1; add_asserts b2);
if emit_other then (add_asserts b1; add_asserts b2);
s
| _ -> s
in
Expand Down
4 changes: 3 additions & 1 deletion src/util/goblintutil.ml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ let arinc_period = if scrambled then "M165" else "PERIOD"
let arinc_time_capacity = if scrambled then "M166" else "TIME_CAPACITY"

let exe_dir = Fpath.(parent (v Sys.executable_name))
let command = String.concat " " (Array.to_list Sys.argv)
let command_line = match Array.to_list Sys.argv with
| command :: arguments -> Filename.quote_command command arguments
| [] -> assert false

(* https://ocaml.org/api/Sys.html#2_SignalnumbersforthestandardPOSIXsignals *)
(* https://ocaml.github.io/ocamlunix/signals.html *)
Expand Down
79 changes: 52 additions & 27 deletions src/util/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1059,27 +1059,6 @@
"description": "Output filename for transformations that output a transformed file.",
"type":"string",
"default": "transformed.c"
},
"assert": {
"title": "trans.assert",
"type": "object",
"properties": {
"full": {
"title": "trans.assert.full",
"description":
"Whether to dump assertions about all local variables or limitting it to modified ones where possible.",
"type": "boolean",
"default": true
},
"only-at-locks":{
"title": "trans.assert.only-at-locks",
"description":
"Only put locks after mutexes have been acquired.",
"type": "boolean",
"default": true
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down Expand Up @@ -1286,6 +1265,7 @@
"title": "exp.architecture",
"description": "Architecture for analysis, currently for witness",
"type": "string",
"enum": ["64bit", "32bit"],
"default": "64bit"
},
"gcc_path": {
Expand Down Expand Up @@ -1753,13 +1733,39 @@
"title": "witness.invariant",
"type": "object",
"properties": {
"nodes": {
"title": "witness.invariant.nodes",
"loop-head": {
"title": "witness.invariant.loop-head",
"description":
"Which witness nodes to add invariants to? all/loop_heads/none",
"type": "string",
"enum": ["all", "loop_heads", "none"],
"default": "all"
"Emit invariants at loop heads",
"type": "boolean",
"default": true
},
"after-lock": {
"title": "witness.invariant.after-lock",
"description":
"Emit invariants after mutex locking",
"type": "boolean",
"default": true
},
"other": {
"title": "witness.invariant.other",
"description":
"Emit invariants at all other locations",
"type": "boolean",
"default": true
},
"split-conjunction": {
"title": "witness.invariant.split-conjunction",
"description": "Split conjunctive invariant to multiple invariants",
"type": "boolean",
"default": true
},
"full": {
"title": "witness.invariant.full",
"description":
"Whether to dump assertions about all local variables or limitting it to modified ones where possible.",
"type": "boolean",
"default": true
}
},
"additionalProperties": false
Expand Down Expand Up @@ -1788,6 +1794,25 @@
"description": "Output witness for unknown result",
"type": "boolean",
"default": true
},
"yaml": {
"title": "witness.yaml",
"type": "object",
"properties": {
"enabled": {
"title": "witness.yaml.enabled",
"description": "Output YAML witness",
"type": "boolean",
"default": false
},
"path": {
"title": "witness.yaml.path",
"description": "YAML witness output path",
"type": "string",
"default": "witness.yml"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down
6 changes: 1 addition & 5 deletions src/util/sarif.ml
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,12 @@ let artifacts_of_messages (messages: Messages.Message.t list): Artifact.t list =
|> List.of_enum

let to_yojson messages =
let commandLine = match Array.to_list Sys.argv with
| command :: arguments -> Filename.quote_command command arguments
| [] -> assert false
in
SarifLog.to_yojson {
version = "2.1.0";
schema = "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json";
runs = [{
invocations = [{
commandLine;
commandLine = Goblintutil.command_line;
executionSuccessful = true;
}];
artifacts = artifacts_of_messages messages;
Expand Down
3 changes: 1 addition & 2 deletions src/witness/myARG.ml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
open WitnessUtil
open MyCFG

module type Node =
Expand Down Expand Up @@ -68,7 +67,7 @@ end
module StackNode (Node: Node):
Node with type t = Node.t list =
struct
include HashedList (Node)
type t = Node.t list [@@deriving eq, hash]

let cfgnode nl = Node.cfgnode (List.hd nl)
let to_string nl =
Expand Down
2 changes: 1 addition & 1 deletion src/witness/svcomp.ml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ sig
val file: Cil.file
val specification: Specification.t

module Cfg: MyCFG.CfgForward
module Cfg: MyCFG.CfgBidir
end

let task: (module Task) option ref = ref None
Expand Down
Loading