From 40d33d552d2a819290e78ee6b058f75bf9797929 Mon Sep 17 00:00:00 2001 From: "xavier.thirioux" <xavier.thirioux@enseeiht.fr> Date: Wed, 5 Apr 2017 16:53:42 +0200 Subject: [PATCH] first version (doesn't even compile) of mutation and test generation standalone command: lustret mostly a recovery of an ancient svn repo: mutations --- Makefile.in | 12 +- lustrec.odocl | 4 + src/Makefile.in | 10 +- src/main_lustre_compiler.ml | 2 +- src/main_lustre_testgen.ml | 184 +++++++++++ src/mmap.ml | 337 +++++++++++++++++++ src/mutation.ml | 642 ++++++++++++++++++++++++++++++++++++ src/options.ml | 51 ++- src/pathConditions.ml | 162 +++++++++ 9 files changed, 1384 insertions(+), 20 deletions(-) create mode 100644 src/main_lustre_testgen.ml create mode 100644 src/mmap.ml create mode 100644 src/mutation.ml create mode 100644 src/pathConditions.ml diff --git a/Makefile.in b/Makefile.in index d22ebb2e..5eb51426 100644 --- a/Makefile.in +++ b/Makefile.in @@ -11,12 +11,20 @@ LUSI_MPFR_LIB=include/mpfr_lustre.lusi LOCAL_BINDIR=bin LOCAL_DOCDIR=doc/manual +all: $(LOCAL_BINDIR)/lustrec $(LOCAL_BINDIR)/lustret + $(LOCAL_BINDIR)/lustrec: configure Makefile @echo Compiling binary lustrec - @$(OCAMLBUILD) -cflags -I,@OCAMLGRAPH_PATH@ -lflag @OCAMLGRAPH_PATH@/graph.cmxa -lflag nums.cmxa -I src -I src/backends/C -I src/plugins/scopes src/main_lustre_compiler.native + @$(OCAMLBUILD) -cflags -I,@OCAMLGRAPH_PATH@ -lflag @OCAMLGRAPH_PATH@/graph.cmxa -lflag -I src -I src/backends/C -I src/plugins/scopes src/main_lustre_compiler.native @mkdir -p $(LOCAL_BINDIR) @mv _build/src/main_lustre_compiler.native $(LOCAL_BINDIR)/lustrec +$(LOCAL_BINDIR)/lustret: configure Makefile + @echo Compiling binary lustret + @$(OCAMLBUILD) -cflags -I,@OCAMLGRAPH_PATH@ -lflag @OCAMLGRAPH_PATH@/graph.cmxa -lflag nums.cmxa -I src -I src/backends/C -I src/plugins/scopes src/main_lustre_testgen.native + @mkdir -p $(LOCAL_BINDIR) + @mv _build/src/main_lustre_testgen.native $(LOCAL_BINDIR)/lustret + configure: configure.ac @echo configure.ac has changed relaunching autoconf @autoconf @@ -74,5 +82,5 @@ install: clean-lusic compile-lusi compile-mpfr-lusi mkdir -p ${datadir} install -m 0655 share/FindLustre.cmake ${datadir} -.PHONY: compile-lusi doc dot lustrec lustrec.odocl clean install dist-clean +.PHONY: compile-lusi doc dot lustrec lustret lustrec.odocl clean install dist-clean diff --git a/lustrec.odocl b/lustrec.odocl index 6e869690..f6d36e4a 100644 --- a/lustrec.odocl +++ b/lustrec.odocl @@ -33,13 +33,17 @@ Log Lusic LustreSpec Machine_code +Mmap +Mutation Main_lustre_compiler +Main_lustre_testgen Modules Mpfr Normalization Optimize_machine Optimize_prog Options +PathConditions Parse Parser_lustre Plugins diff --git a/src/Makefile.in b/src/Makefile.in index 126a31f7..8b5b5580 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -10,12 +10,20 @@ LUSI_LIBS=include/math.lusi include/conv.lusi LOCAL_BINDIR=../bin LOCAL_DOCDIR=../doc/manual +all: lustrec lustret + lustrec: @echo Compiling binary lustrec @$(OCAMLBUILD) main_lustre_compiler.native @mkdir -p $(LOCAL_BINDIR) @mv _build/main_lustre_compiler.native $(LOCAL_BINDIR)/lustrec +lustret: + @echo Compiling binary lustret + @$(OCAMLBUILD) main_lustre_testgen.native + @mkdir -p $(LOCAL_BINDIR) + @mv _build/main_lustre_testgen.native $(LOCAL_BINDIR)/lustret + doc: @echo Generating doc @$(OCAMLBUILD) lustrec.docdir/index.html @@ -37,5 +45,5 @@ dist-clean: clean install: make -C .. install -.PHONY: compile-lusi doc dot lustrec lustrec.odocl clean install dist-clean +.PHONY: compile-lusi doc dot lustrec lustret lustrec.odocl clean install dist-clean diff --git a/src/main_lustre_compiler.ml b/src/main_lustre_compiler.ml index 8c8f549c..2e94e7f5 100644 --- a/src/main_lustre_compiler.ml +++ b/src/main_lustre_compiler.ml @@ -457,7 +457,7 @@ let _ = try Printexc.record_backtrace true; - let options = Options.options @ + let options = Options.lustrec_options @ List.flatten ( List.map Options.plugin_opt [ Scopes.Plugin.name, Scopes.Plugin.activate, Scopes.Plugin.options diff --git a/src/main_lustre_testgen.ml b/src/main_lustre_testgen.ml new file mode 100644 index 00000000..e0e752bd --- /dev/null +++ b/src/main_lustre_testgen.ml @@ -0,0 +1,184 @@ +(********************************************************************) +(* *) +(* The LustreC compiler toolset / The LustreC Development Team *) +(* Copyright 2012 - -- ONERA - CNRS - INPT *) +(* *) +(* LustreC is free software, distributed WITHOUT ANY WARRANTY *) +(* under the terms of the GNU Lesser General Public License *) +(* version 2.1. *) +(* *) +(********************************************************************) + +(* This module is used for the lustre test generator *) + +open Format +open Log + +open Utils +open LustreSpec +open Compiler_common + +let usage = "Usage: lustret [options] \x1b[4msource file\x1b[0m" + +let extensions = [".lus"] + +(* From prog to prog *) +let stage1 prog dirname basename = + (* Removing automata *) + let prog = expand_automata prog in + + Log.report ~level:4 (fun fmt -> fprintf fmt ".. after automata expansion:@.@[<v 2>@ %a@]@," Printers.pp_prog prog); + + (* Importing source *) + let _ = Modules.load_program ISet.empty prog in + + (* Extracting dependencies *) + let dependencies, type_env, clock_env = import_dependencies prog in + + (* Sorting nodes *) + let prog = SortProg.sort prog in + + (* Perform inlining before any analysis *) + let orig, prog = + if !Options.global_inline && !Options.main_node <> "" then + (if !Options.witnesses then prog else []), + Inliner.global_inline basename prog type_env clock_env + else (* if !Option.has_local_inline *) + [], + Inliner.local_inline basename prog type_env clock_env + in + + check_stateless_decls prog; + + (* Typing *) + let computed_types_env = type_decls type_env prog in + + (* Clock calculus *) + let computed_clocks_env = clock_decls clock_env prog in + + (* Creating destination directory if needed *) + create_dest_dir (); + + (* Compatibility with Lusi *) + (* Checking the existence of a lusi (Lustre Interface file) *) + let extension = ".lusi" in + compile_source_to_header prog computed_types_env computed_clocks_env dirname basename extension; + + Typing.uneval_prog_generics prog; + Clock_calculus.uneval_prog_generics prog; + + if !Options.global_inline && !Options.main_node <> "" && !Options.witnesses then + begin + let orig = Corelang.copy_prog orig in + Log.report ~level:1 (fun fmt -> fprintf fmt ".. generating witness file@,"); + check_stateless_decls orig; + let _ = Typing.type_prog type_env orig in + let _ = Clock_calculus.clock_prog clock_env orig in + Typing.uneval_prog_generics orig; + Clock_calculus.uneval_prog_generics orig; + Inliner.witness + basename + !Options.main_node + orig prog type_env clock_env + end; + + (* Normalization phase *) + Log.report ~level:1 (fun fmt -> fprintf fmt ".. normalization@,"); + (* Special treatment of arrows in lustre backend. We want to keep them *) + if !Options.output = "lustre" then + Normalization.unfold_arrow_active := false; + let prog = Normalization.normalize_prog prog in + Log.report ~level:2 (fun fmt -> fprintf fmt "@[<v 2>@ %a@]@," Printers.pp_prog prog); + + prog, dependencies + +let testgen_source dirname basename extension = + let source_name = dirname ^ "/" ^ basename ^ extension in + + Log.report ~level:1 (fun fmt -> fprintf fmt "@[<v>"); + + (* Parsing source *) + let prog = parse_source source_name in + + let prog, dependencies = stage1 prog dirname basename in + + if !Options.gen_mcdc then ( + PathConditions.mcdc prog; + exit 0 + ) ; + (* generate mutants *) + let mutants, mutation_printer = Mutation.mutate !Options.nb_mutants prog in + + (* Print generated mutants in target directory. *) + let cpt = ref 0 in + List.iter (fun (mutation, mutant) -> + (* Debugging code *) + (* if List.mem !cpt [238;371;601;799;875;998] then *) + (* Format.eprintf "Mutant %i: %a -> %a" !cpt Printers.pp_expr orig_e Printers.pp_expr new_e *) + (* ; *) + incr cpt; + let mutant_filename = + match !Options.dest_dir with + | "" -> (* Mutants are generated in source directory *) + basename^ ".mutant.n" ^ (string_of_int !cpt) ^ extension + | dir -> (* Mutants are generated in target directory *) + dir ^ "/" ^ (Filename.basename basename)^ ".mutant.n" ^ (string_of_int !cpt) ^ extension + in + let mutant_out = ( + try + open_out mutant_filename + with + Sys_error _ -> Format.eprintf "Unable to open file %s for writing.@." mutant_filename; exit 1 + ) + in + let mutant_fmt = formatter_of_out_channel mutant_out in + report ~level:1 (fun fmt -> fprintf fmt ".. generating mutant %s: %a@,@?" mutant_filename mutation_printer mutation); + Format.fprintf mutant_fmt "%a@." Printers.pp_prog mutant + ) + mutants; + Log.report ~level:1 (fun fmt -> fprintf fmt ".. done @ @]@."); + (* We stop the process here *) + exit 0 + +let testgen dirname basename extension = + match extension with + | ".lus" -> testgen_source dirname basename extension + | _ -> assert false + +let anonymous filename = + let ok_ext, ext = List.fold_left + (fun (ok, ext) ext' -> + if not ok && Filename.check_suffix filename ext' then + true, ext' + else + ok, ext) + (false, "") extensions in + if ok_ext then + let dirname = Filename.dirname filename in + let basename = Filename.chop_suffix (Filename.basename filename) ext in + testgen dirname basename ext + else + raise (Arg.Bad ("Can only compile *.lus files")) + +let _ = + Global.initialize (); + Corelang.add_internal_funs (); + try + Printexc.record_backtrace true; + + let options = Options.lustret_options + + in + + Arg.parse options anonymous usage + with + | Parse.Error _ + | Types.Error (_,_) | Clocks.Error (_,_) + | Corelang.Error _ (*| Task_set.Error _*) + | Causality.Error _ -> exit 1 + | Sys_error msg -> (eprintf "Failure: %s@." msg) + | exc -> (Utils.track_exception (); raise exc) + +(* Local Variables: *) +(* compile-command:"make -C .." *) +(* End: *) diff --git a/src/mmap.ml b/src/mmap.ml new file mode 100644 index 00000000..7d65bc6b --- /dev/null +++ b/src/mmap.ml @@ -0,0 +1,337 @@ +(***********************************************************************) +(* *) +(* OCaml *) +(* *) +(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *) +(* *) +(* Copyright 1996 Institut National de Recherche en Informatique et *) +(* en Automatique. All rights reserved. This file is distributed *) +(* under the terms of the GNU Library General Public License, with *) +(* the special exception on linking described in file ../LICENSE. *) +(* *) +(***********************************************************************) + +module type OrderedType = + sig + type t + val compare: t -> t -> int + end + +module type S = + sig + type key + type +'a t + val empty: 'a t + val is_empty: 'a t -> bool + val mem: key -> 'a t -> bool + val add: key -> 'a -> 'a t -> 'a t + val singleton: key -> 'a -> 'a t + val remove: key -> 'a t -> 'a t + val merge: + (key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t + val compare: ('a -> 'a -> int) -> 'a t -> 'a t -> int + val equal: ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + val iter: (key -> 'a -> unit) -> 'a t -> unit + val fold: (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b + val for_all: (key -> 'a -> bool) -> 'a t -> bool + val exists: (key -> 'a -> bool) -> 'a t -> bool + val filter: (key -> 'a -> bool) -> 'a t -> 'a t + val partition: (key -> 'a -> bool) -> 'a t -> 'a t * 'a t + val cardinal: 'a t -> int + val bindings: 'a t -> (key * 'a) list + val min_binding: 'a t -> (key * 'a) + val max_binding: 'a t -> (key * 'a) + val choose: 'a t -> (key * 'a) + val split: key -> 'a t -> 'a t * 'a option * 'a t + val find: key -> 'a t -> 'a + val map: ('a -> 'b) -> 'a t -> 'b t + val mapi: (key -> 'a -> 'b) -> 'a t -> 'b t + end + +module Make(Ord: OrderedType) = struct + + type key = Ord.t + + type 'a t = + Empty + | Node of 'a t * key * 'a * 'a t * int + + let height = function + Empty -> 0 + | Node(_,_,_,_,h) -> h + + let create l x d r = + let hl = height l and hr = height r in + Node(l, x, d, r, (if hl >= hr then hl + 1 else hr + 1)) + + let singleton x d = Node(Empty, x, d, Empty, 1) + + let bal l x d r = + let hl = match l with Empty -> 0 | Node(_,_,_,_,h) -> h in + let hr = match r with Empty -> 0 | Node(_,_,_,_,h) -> h in + if hl > hr + 2 then begin + match l with + Empty -> invalid_arg "Map.bal" + | Node(ll, lv, ld, lr, _) -> + if height ll >= height lr then + create ll lv ld (create lr x d r) + else begin + match lr with + Empty -> invalid_arg "Map.bal" + | Node(lrl, lrv, lrd, lrr, _)-> + create (create ll lv ld lrl) lrv lrd (create lrr x d r) + end + end else if hr > hl + 2 then begin + match r with + Empty -> invalid_arg "Map.bal" + | Node(rl, rv, rd, rr, _) -> + if height rr >= height rl then + create (create l x d rl) rv rd rr + else begin + match rl with + Empty -> invalid_arg "Map.bal" + | Node(rll, rlv, rld, rlr, _) -> + create (create l x d rll) rlv rld (create rlr rv rd rr) + end + end else + Node(l, x, d, r, (if hl >= hr then hl + 1 else hr + 1)) + + let empty = Empty + + let is_empty = function Empty -> true | _ -> false + + let rec add x data = function + Empty -> + Node(Empty, x, data, Empty, 1) + | Node(l, v, d, r, h) -> + let c = Ord.compare x v in + if c = 0 then + Node(l, x, data, r, h) + else if c < 0 then + bal (add x data l) v d r + else + bal l v d (add x data r) + + let rec find x = function + Empty -> + raise Not_found + | Node(l, v, d, r, _) -> + let c = Ord.compare x v in + if c = 0 then d + else find x (if c < 0 then l else r) + + let rec mem x = function + Empty -> + false + | Node(l, v, d, r, _) -> + let c = Ord.compare x v in + c = 0 || mem x (if c < 0 then l else r) + + let rec min_binding = function + Empty -> raise Not_found + | Node(Empty, x, d, r, _) -> (x, d) + | Node(l, x, d, r, _) -> min_binding l + + let rec max_binding = function + Empty -> raise Not_found + | Node(l, x, d, Empty, _) -> (x, d) + | Node(l, x, d, r, _) -> max_binding r + + let rec remove_min_binding = function + Empty -> invalid_arg "Map.remove_min_elt" + | Node(Empty, x, d, r, _) -> r + | Node(l, x, d, r, _) -> bal (remove_min_binding l) x d r + + let merge t1 t2 = + match (t1, t2) with + (Empty, t) -> t + | (t, Empty) -> t + | (_, _) -> + let (x, d) = min_binding t2 in + bal t1 x d (remove_min_binding t2) + + let rec remove x = function + Empty -> + Empty + | Node(l, v, d, r, h) -> + let c = Ord.compare x v in + if c = 0 then + merge l r + else if c < 0 then + bal (remove x l) v d r + else + bal l v d (remove x r) + + let rec iter f = function + Empty -> () + | Node(l, v, d, r, _) -> + iter f l; f v d; iter f r + + let rec map f = function + Empty -> + Empty + | Node(l, v, d, r, h) -> + let l' = map f l in + let d' = f d in + let r' = map f r in + Node(l', v, d', r', h) + + let rec mapi f = function + Empty -> + Empty + | Node(l, v, d, r, h) -> + let l' = mapi f l in + let d' = f v d in + let r' = mapi f r in + Node(l', v, d', r', h) + + let rec fold f m accu = + match m with + Empty -> accu + | Node(l, v, d, r, _) -> + fold f r (f v d (fold f l accu)) + + let rec for_all p = function + Empty -> true + | Node(l, v, d, r, _) -> p v d && for_all p l && for_all p r + + let rec exists p = function + Empty -> false + | Node(l, v, d, r, _) -> p v d || exists p l || exists p r + + (* Beware: those two functions assume that the added k is *strictly* + smaller (or bigger) than all the present keys in the tree; it + does not test for equality with the current min (or max) key. + + Indeed, they are only used during the "join" operation which + respects this precondition. + *) + + let rec add_min_binding k v = function + | Empty -> singleton k v + | Node (l, x, d, r, h) -> + bal (add_min_binding k v l) x d r + + let rec add_max_binding k v = function + | Empty -> singleton k v + | Node (l, x, d, r, h) -> + bal l x d (add_max_binding k v r) + + (* Same as create and bal, but no assumptions are made on the + relative heights of l and r. *) + + let rec join l v d r = + match (l, r) with + (Empty, _) -> add_min_binding v d r + | (_, Empty) -> add_max_binding v d l + | (Node(ll, lv, ld, lr, lh), Node(rl, rv, rd, rr, rh)) -> + if lh > rh + 2 then bal ll lv ld (join lr v d r) else + if rh > lh + 2 then bal (join l v d rl) rv rd rr else + create l v d r + + (* Merge two trees l and r into one. + All elements of l must precede the elements of r. + No assumption on the heights of l and r. *) + + let concat t1 t2 = + match (t1, t2) with + (Empty, t) -> t + | (t, Empty) -> t + | (_, _) -> + let (x, d) = min_binding t2 in + join t1 x d (remove_min_binding t2) + + let concat_or_join t1 v d t2 = + match d with + | Some d -> join t1 v d t2 + | None -> concat t1 t2 + + let rec split x = function + Empty -> + (Empty, None, Empty) + | Node(l, v, d, r, _) -> + let c = Ord.compare x v in + if c = 0 then (l, Some d, r) + else if c < 0 then + let (ll, pres, rl) = split x l in (ll, pres, join rl v d r) + else + let (lr, pres, rr) = split x r in (join l v d lr, pres, rr) + + let rec merge f s1 s2 = + match (s1, s2) with + (Empty, Empty) -> Empty + | (Node (l1, v1, d1, r1, h1), _) when h1 >= height s2 -> + let (l2, d2, r2) = split v1 s2 in + concat_or_join (merge f l1 l2) v1 (f v1 (Some d1) d2) (merge f r1 r2) + | (_, Node (l2, v2, d2, r2, h2)) -> + let (l1, d1, r1) = split v2 s1 in + concat_or_join (merge f l1 l2) v2 (f v2 d1 (Some d2)) (merge f r1 r2) + | _ -> + assert false + + let rec filter p = function + Empty -> Empty + | Node(l, v, d, r, _) -> + (* call [p] in the expected left-to-right order *) + let l' = filter p l in + let pvd = p v d in + let r' = filter p r in + if pvd then join l' v d r' else concat l' r' + + let rec partition p = function + Empty -> (Empty, Empty) + | Node(l, v, d, r, _) -> + (* call [p] in the expected left-to-right order *) + let (lt, lf) = partition p l in + let pvd = p v d in + let (rt, rf) = partition p r in + if pvd + then (join lt v d rt, concat lf rf) + else (concat lt rt, join lf v d rf) + + type 'a enumeration = End | More of key * 'a * 'a t * 'a enumeration + + let rec cons_enum m e = + match m with + Empty -> e + | Node(l, v, d, r, _) -> cons_enum l (More(v, d, r, e)) + + let compare cmp m1 m2 = + let rec compare_aux e1 e2 = + match (e1, e2) with + (End, End) -> 0 + | (End, _) -> -1 + | (_, End) -> 1 + | (More(v1, d1, r1, e1), More(v2, d2, r2, e2)) -> + let c = Ord.compare v1 v2 in + if c <> 0 then c else + let c = cmp d1 d2 in + if c <> 0 then c else + compare_aux (cons_enum r1 e1) (cons_enum r2 e2) + in compare_aux (cons_enum m1 End) (cons_enum m2 End) + + let equal cmp m1 m2 = + let rec equal_aux e1 e2 = + match (e1, e2) with + (End, End) -> true + | (End, _) -> false + | (_, End) -> false + | (More(v1, d1, r1, e1), More(v2, d2, r2, e2)) -> + Ord.compare v1 v2 = 0 && cmp d1 d2 && + equal_aux (cons_enum r1 e1) (cons_enum r2 e2) + in equal_aux (cons_enum m1 End) (cons_enum m2 End) + + let rec cardinal = function + Empty -> 0 + | Node(l, _, _, r, _) -> cardinal l + 1 + cardinal r + + let rec bindings_aux accu = function + Empty -> accu + | Node(l, v, d, r, _) -> bindings_aux ((v, d) :: bindings_aux accu r) l + + let bindings s = + bindings_aux [] s + + let choose = min_binding + +end diff --git a/src/mutation.ml b/src/mutation.ml new file mode 100644 index 00000000..f2e40022 --- /dev/null +++ b/src/mutation.ml @@ -0,0 +1,642 @@ +open Corelang +open Log +open Format + +let random_seed = ref 0 +let threshold_delay = 95 +let threshold_inc_int = 97 +let threshold_dec_int = 97 +let threshold_random_int = 96 +let threshold_switch_int = 100 (* not implemented yet *) +let threshold_random_float = 100 (* not used yet *) +let threshold_negate_bool_var = 95 +let threshold_arith_op = 95 +let threshold_rel_op = 95 +let threshold_bool_op = 95 + +let int_consts = ref [] + +let rename_app id = + if !Options.no_mutation_suffix then + id + else + id ^ "_mutant" + +(************************************************************************************) +(* Gathering constants in the code *) +(************************************************************************************) + +module IntSet = Set.Make (struct type t = int let compare = compare end) +module OpCount = Mmap.Make (struct type t = string let compare = compare end) + +type records = { + consts: IntSet.t; + nb_boolexpr: int; + nb_pre: int; + nb_op: int OpCount.t; +} + +let arith_op = ["+" ; "-" ; "*" ; "/"] +let bool_op = ["&&"; "||"; "xor"; "impl"] +let rel_op = ["<" ; "<=" ; ">" ; ">=" ; "!=" ; "=" ] +let ops = arith_op @ bool_op @ rel_op +let all_ops = "not" :: ops + +let empty_records = + {consts=IntSet.empty; nb_boolexpr=0; nb_pre=0; nb_op=OpCount.empty} + +let records = ref empty_records + +let merge_records records_list = + let merge_record r1 r2 = + { + consts = IntSet.union r1.consts r2.consts; + + nb_boolexpr = r1.nb_boolexpr + r2.nb_boolexpr; + nb_pre = r1.nb_pre + r2.nb_pre; + + nb_op = OpCount.merge (fun op r1opt r2opt -> + match r1opt, r2opt with + | None, _ -> r2opt + | _, None -> r1opt + | Some x, Some y -> Some (x+y) + ) r1.nb_op r2.nb_op + } + in + List.fold_left merge_record empty_records records_list + +let compute_records_const_value c = + match c with + | Const_int i -> {empty_records with consts = IntSet.singleton i} + | _ -> empty_records + +let rec compute_records_expr expr = + let boolexpr = + if (Types.repr expr.expr_type).Types.tdesc = Types.Tbool then + {empty_records with nb_boolexpr = 1} + else + empty_records + in + let subrec = + match expr.expr_desc with + | Expr_const c -> compute_records_const_value c + | Expr_tuple l -> merge_records (List.map compute_records_expr l) + | Expr_ite (i,t,e) -> + merge_records (List.map compute_records_expr [i;t;e]) + | Expr_arrow (e1, e2) -> + merge_records (List.map compute_records_expr [e1;e2]) + | Expr_pre e -> + merge_records ( + ({empty_records with nb_pre = 1}) + ::[compute_records_expr e]) + | Expr_appl (op_id, args, r) -> + if List.mem op_id ops then + merge_records ( + ({empty_records with nb_op = OpCount.singleton op_id 1}) + ::[compute_records_expr args]) + else + compute_records_expr args + | _ -> empty_records + in + merge_records [boolexpr;subrec] + +let compute_records_eq eq = compute_records_expr eq.eq_rhs + +let compute_records_node nd = + merge_records (List.map compute_records_eq nd.node_eqs) + +let compute_records_top_decl td = + match td.top_decl_desc with + | Node nd -> compute_records_node nd + | Consts constsl -> merge_records (List.map (fun c -> compute_records_const_value c.const_value) constsl) + | _ -> empty_records + +let compute_records prog = + merge_records (List.map compute_records_top_decl prog) + +(*****************************************************************) +(* Random mutation *) +(*****************************************************************) + +let check_mut e1 e2 = + let rec eq e1 e2 = + match e1.expr_desc, e2.expr_desc with + | Expr_const c1, Expr_const c2 -> c1 = c2 + | Expr_ident id1, Expr_ident id2 -> id1 = id2 + | Expr_tuple el1, Expr_tuple el2 -> List.length el1 = List.length el2 && List.for_all2 eq el1 el2 + | Expr_ite (i1, t1, e1), Expr_ite (i2, t2, e2) -> eq i1 i2 && eq t1 t2 && eq e1 e2 + | Expr_arrow (x1, y1), Expr_arrow (x2, y2) -> eq x1 x2 && eq y1 y2 + | Expr_pre e1, Expr_pre e2 -> eq e1 e2 + | Expr_appl (id1, e1, _), Expr_appl (id2, e2, _) -> id1 = id2 && eq e1 e2 + | _ -> false + in + if not (eq e1 e2) then + Some (e1, e2) + else + None + +let mk_cst_expr c = mkexpr Location.dummy_loc (Expr_const c) + +let rdm_mutate_int i = + if Random.int 100 > threshold_inc_int then + i+1 + else if Random.int 100 > threshold_dec_int then + i-1 + else if Random.int 100 > threshold_random_int then + Random.int 10 + else if Random.int 100 > threshold_switch_int then + let idx = Random.int (List.length !int_consts) in + List.nth !int_consts idx + else + i + +let rdm_mutate_float f = + if Random.int 100 > threshold_random_float then + Random.float 10. + else + f + +let rdm_mutate_op op = +match op with +| "+" | "-" | "*" | "/" when Random.int 100 > threshold_arith_op -> + let filtered = List.filter (fun x -> x <> op) ["+"; "-"; "*"; "/"] in + List.nth filtered (Random.int 3) +| "&&" | "||" | "xor" | "impl" when Random.int 100 > threshold_bool_op -> + let filtered = List.filter (fun x -> x <> op) ["&&"; "||"; "xor"; "impl"] in + List.nth filtered (Random.int 3) +| "<" | "<=" | ">" | ">=" | "!=" | "=" when Random.int 100 > threshold_rel_op -> + let filtered = List.filter (fun x -> x <> op) ["<"; "<="; ">"; ">="; "!="; "="] in + List.nth filtered (Random.int 5) +| _ -> op + + +let rdm_mutate_var expr = + match (Types.repr expr.expr_type).Types.tdesc with + | Types.Tbool -> + (* if Random.int 100 > threshold_negate_bool_var then *) + let new_e = mkpredef_unary_call Location.dummy_loc "not" expr in + Some (expr, new_e), new_e + (* else *) + (* expr *) + | _ -> None, expr + +let rdm_mutate_pre orig_expr = + let new_e = Expr_pre orig_expr in + Some (orig_expr, {orig_expr with expr_desc = new_e}), new_e + + +let rdm_mutate_const_value c = + match c with + | Const_int i -> Const_int (rdm_mutate_int i) + | Const_real s -> Const_real s (* those are string, let's leave them *) + | Const_float f -> Const_float (rdm_mutate_float f) + | Const_array _ + | Const_tag _ -> c + +let rdm_mutate_const c = + let new_const = rdm_mutate_const_value c.const_value in + let mut = check_mut (mk_cst_expr c.const_value) (mk_cst_expr new_const) in + mut, { c with const_value = new_const } + + +let select_in_list list rdm_mutate_elem = + let selected = Random.int (List.length list) in + let mutation_opt, new_list, _ = + List.fold_right + (fun elem (mutation_opt, res, cpt) -> if cpt = selected then + let mutation, new_elem = rdm_mutate_elem elem in + Some mutation, new_elem::res, cpt+1 else mutation_opt, elem::res, cpt+1) + list + (None, [], 0) + in + match mutation_opt with + | Some mut -> mut, new_list + | _ -> assert false + + +let rec rdm_mutate_expr expr = + let mk_e d = { expr with expr_desc = d } in + match expr.expr_desc with + | Expr_ident id -> rdm_mutate_var expr + | Expr_const c -> + let new_const = rdm_mutate_const_value c in + let mut = check_mut (mk_cst_expr c) (mk_cst_expr new_const) in + mut, mk_e (Expr_const new_const) + | Expr_tuple l -> + let mut, l' = select_in_list l rdm_mutate_expr in + mut, mk_e (Expr_tuple l') + | Expr_ite (i,t,e) -> + let mut, [i'; t'; e'] = select_in_list [i; t; e] rdm_mutate_expr in + mut, mk_e (Expr_ite (i', t', e')) + | Expr_arrow (e1, e2) -> + let mut, [e1'; e2'] = select_in_list [e1; e2] rdm_mutate_expr in + mut, mk_e (Expr_arrow (e1', e2')) + | Expr_pre e -> + let select_pre = Random.bool () in + if select_pre then + let mut, new_expr = rdm_mutate_pre expr in + mut, mk_e new_expr + else + let mut, e' = rdm_mutate_expr e in + mut, mk_e (Expr_pre e') + | Expr_appl (op_id, args, r) -> + let select_op = Random.bool () in + if select_op then + let new_op_id = rdm_mutate_op op_id in + let new_e = mk_e (Expr_appl (new_op_id, args, r)) in + let mut = check_mut expr new_e in + mut, new_e + else + let mut, new_args = rdm_mutate_expr args in + mut, mk_e (Expr_appl (op_id, new_args, r)) + + (* Other constructs are kept. + | Expr_fby of expr * expr + | Expr_array of expr list + | Expr_access of expr * Dimension.dim_expr + | Expr_power of expr * Dimension.dim_expr + | Expr_when of expr * ident * label + | Expr_merge of ident * (label * expr) list + | Expr_uclock of expr * int + | Expr_dclock of expr * int + | Expr_phclock of expr * rat *) + (* | _ -> expr.expr_desc *) + + +let rdm_mutate_eq eq = + let mutation, new_rhs = rdm_mutate_expr eq.eq_rhs in + mutation, { eq with eq_rhs = new_rhs } + +let rdm_mutate_node nd = + let mutation, new_node_eqs = + select_in_list + nd.node_eqs + (fun eq -> let mut, new_eq = rdm_mutate_eq eq in + report ~level:1 + (fun fmt -> fprintf fmt "mutation: %a becomes %a@." + Printers.pp_node_eq eq + Printers.pp_node_eq new_eq); + mut, new_eq ) + in + mutation, { nd with node_eqs = new_node_eqs } + +let rdm_mutate_top_decl td = + match td.top_decl_desc with + | Node nd -> + let mutation, new_node = rdm_mutate_node nd in + mutation, { td with top_decl_desc = Node new_node} + | Consts constsl -> + let mut, new_constsl = select_in_list constsl rdm_mutate_const in + mut, { td with top_decl_desc = Consts new_constsl } + | _ -> None, td + +(* Create a single mutant with the provided random seed *) +let rdm_mutate_prog prog = + select_in_list prog rdm_mutate_top_decl + +let rdm_mutate nb prog = + let rec iterate nb res = + incr random_seed; + if nb <= 0 then + res + else ( + Random.init !random_seed; + let mutation, new_mutant = rdm_mutate_prog prog in + match mutation with + None -> iterate nb res + | Some mutation -> ( + if List.mem_assoc mutation res then ( + iterate nb res + ) + else ( + report ~level:1 (fun fmt -> fprintf fmt "%i mutants remaining@." nb); + iterate (nb-1) ((mutation, new_mutant)::res) + ) + ) + ) + in + iterate nb [] + + +(*****************************************************************) +(* Random mutation *) +(*****************************************************************) + +type mutant_t = Boolexpr of int | Pre of int | Op of string * int * string | IncrIntCst of int | DecrIntCst of int | SwitchIntCst of int * int + +let target : mutant_t option ref = ref None + +let print_directive fmt d = + match d with + | Pre n -> Format.fprintf fmt "pre %i" n + | Boolexpr n -> Format.fprintf fmt "boolexpr %i" n + | Op (o, i, d) -> Format.fprintf fmt "%s %i -> %s" o i d + | IncrIntCst n -> Format.fprintf fmt "incr int cst %i" n + | DecrIntCst n -> Format.fprintf fmt "decr int cst %i" n + | SwitchIntCst (n, m) -> Format.fprintf fmt "switch int cst %i -> %i" n m + +let fold_mutate_int i = + if Random.int 100 > threshold_inc_int then + i+1 + else if Random.int 100 > threshold_dec_int then + i-1 + else if Random.int 100 > threshold_random_int then + Random.int 10 + else if Random.int 100 > threshold_switch_int then + try + let idx = Random.int (List.length !int_consts) in + List.nth !int_consts idx + with _ -> i + else + i + +let fold_mutate_float f = + if Random.int 100 > threshold_random_float then + Random.float 10. + else + f + +let fold_mutate_op op = +(* match op with *) +(* | "+" | "-" | "*" | "/" when Random.int 100 > threshold_arith_op -> *) +(* let filtered = List.filter (fun x -> x <> op) ["+"; "-"; "*"; "/"] in *) +(* List.nth filtered (Random.int 3) *) +(* | "&&" | "||" | "xor" | "impl" when Random.int 100 > threshold_bool_op -> *) +(* let filtered = List.filter (fun x -> x <> op) ["&&"; "||"; "xor"; "impl"] in *) +(* List.nth filtered (Random.int 3) *) +(* | "<" | "<=" | ">" | ">=" | "!=" | "=" when Random.int 100 > threshold_rel_op -> *) +(* let filtered = List.filter (fun x -> x <> op) ["<"; "<="; ">"; ">="; "!="; "="] in *) +(* List.nth filtered (Random.int 5) *) +(* | _ -> op *) + match !target with + | Some (Op(op_orig, 0, op_new)) when op_orig = op -> ( + target := None; + op_new + ) + | Some (Op(op_orig, n, op_new)) when op_orig = op -> ( + target := Some (Op(op_orig, n-1, op_new)); + op + ) + | _ -> if List.mem op Basic_library.internal_funs then op else rename_app op + + +let fold_mutate_var expr = + (* match (Types.repr expr.expr_type).Types.tdesc with *) + (* | Types.Tbool -> *) + (* (\* if Random.int 100 > threshold_negate_bool_var then *\) *) + (* mkpredef_unary_call Location.dummy_loc "not" expr *) + (* (\* else *\) *) + (* (\* expr *\) *) + (* | _ -> + *)expr + +let fold_mutate_boolexpr expr = + match !target with + | Some (Boolexpr 0) -> ( + target := None; + mkpredef_unary_call Location.dummy_loc "not" expr + ) + | Some (Boolexpr n) -> + (target := Some (Boolexpr (n-1)); expr) + | _ -> expr + +let fold_mutate_pre orig_expr e = + match !target with + Some (Pre 0) -> ( + target := None; + Expr_pre ({orig_expr with expr_desc = Expr_pre e}) + ) + | Some (Pre n) -> ( + target := Some (Pre (n-1)); + Expr_pre e + ) + | _ -> Expr_pre e + +let fold_mutate_const_value c = +match c with +| Const_int i -> ( + match !target with + | Some (IncrIntCst 0) -> (target := None; Const_int (i+1)) + | Some (DecrIntCst 0) -> (target := None; Const_int (i-1)) + | Some (SwitchIntCst (0, id)) -> (target := None; Const_int (List.nth (IntSet.elements (IntSet.remove i !records.consts)) id)) + | Some (IncrIntCst n) -> (target := Some (IncrIntCst (n-1)); c) + | Some (DecrIntCst n) -> (target := Some (DecrIntCst (n-1)); c) + | Some (SwitchIntCst (n, id)) -> (target := Some (SwitchIntCst (n-1, id)); c) + | _ -> c) +| _ -> c + +(* + match c with + | Const_int i -> Const_int (fold_mutate_int i) + | Const_real s -> Const_real s (* those are string, let's leave them *) + | Const_float f -> Const_float (fold_mutate_float f) + | Const_array _ + | Const_tag _ -> c +TODO + + *) +let fold_mutate_const c = + { c with const_value = fold_mutate_const_value c.const_value } + +let rec fold_mutate_expr expr = + let new_expr = + match expr.expr_desc with + | Expr_ident id -> fold_mutate_var expr + | _ -> ( + let new_desc = match expr.expr_desc with + | Expr_const c -> Expr_const (fold_mutate_const_value c) + | Expr_tuple l -> Expr_tuple (List.fold_right (fun e res -> (fold_mutate_expr e)::res) l []) + | Expr_ite (i,t,e) -> Expr_ite (fold_mutate_expr i, fold_mutate_expr t, fold_mutate_expr e) + | Expr_arrow (e1, e2) -> Expr_arrow (fold_mutate_expr e1, fold_mutate_expr e2) + | Expr_pre e -> fold_mutate_pre expr (fold_mutate_expr e) + | Expr_appl (op_id, args, r) -> Expr_appl (fold_mutate_op op_id, fold_mutate_expr args, r) + (* Other constructs are kept. + | Expr_fby of expr * expr + | Expr_array of expr list + | Expr_access of expr * Dimension.dim_expr + | Expr_power of expr * Dimension.dim_expr + | Expr_when of expr * ident * label + | Expr_merge of ident * (label * expr) list + | Expr_uclock of expr * int + | Expr_dclock of expr * int + | Expr_phclock of expr * rat *) + | _ -> expr.expr_desc + + in + { expr with expr_desc = new_desc } + ) + in + if (Types.repr expr.expr_type).Types.tdesc = Types.Tbool then + fold_mutate_boolexpr new_expr + else + new_expr + +let fold_mutate_eq eq = + { eq with eq_rhs = fold_mutate_expr eq.eq_rhs } + +let fold_mutate_node nd = + { nd with + node_eqs = + List.fold_right (fun e res -> (fold_mutate_eq e)::res) nd.node_eqs []; + node_id = rename_app nd.node_id + } + +let fold_mutate_top_decl td = + match td.top_decl_desc with + | Node nd -> { td with top_decl_desc = Node (fold_mutate_node nd)} + | Consts constsl -> { td with top_decl_desc = Consts (List.fold_right (fun e res -> (fold_mutate_const e)::res) constsl [])} + | _ -> td + +(* Create a single mutant with the provided random seed *) +let fold_mutate_prog prog = + List.fold_right (fun e res -> (fold_mutate_top_decl e)::res) prog [] + +let create_mutant prog directive = + target := Some directive; + let prog' = fold_mutate_prog prog in + target := None; + prog' + + +let op_mutation op = + let res = + let rem_op l = List.filter (fun e -> e <> op) l in + if List.mem op arith_op then rem_op arith_op else + if List.mem op bool_op then rem_op bool_op else + if List.mem op rel_op then rem_op rel_op else + (Format.eprintf "Failing with op %s@." op; + assert false + ) + in + (* Format.eprintf "Mutation op %s to [%a]@." op (Utils.fprintf_list ~sep:"," Format.pp_print_string) res; *) + res + +let rec remains select list = + match list with + [] -> [] + | hd::tl -> if select hd then tl else remains select tl + +let next_change m = + let res = + let rec first_op () = + try + let min_binding = OpCount.min_binding !records.nb_op in + Op (fst min_binding, 0, List.hd (op_mutation (fst min_binding))) + with Not_found -> first_boolexpr () + and first_boolexpr () = + if !records.nb_boolexpr > 0 then + Boolexpr 0 + else first_pre () + and first_pre () = + if !records.nb_pre > 0 then + Pre 0 + else + first_op () + and first_intcst () = + if IntSet.cardinal !records.consts > 0 then + IncrIntCst 0 + else + first_boolexpr () + in + match m with + | Boolexpr n -> + if n+1 >= !records.nb_boolexpr then + first_pre () + else + Boolexpr (n+1) + | Pre n -> + if n+1 >= !records.nb_pre then + first_op () + else Pre (n+1) + | Op (orig, id, mut_op) -> ( + match remains (fun x -> x = mut_op) (op_mutation orig) with + | next_op::_ -> Op (orig, id, next_op) + | [] -> if id+1 >= OpCount.find orig !records.nb_op then ( + match remains (fun (k1, _) -> k1 = orig) (OpCount.bindings !records.nb_op) with + | [] -> first_intcst () + | hd::_ -> Op (fst hd, 0, List.hd (op_mutation (fst hd))) + ) else + Op(orig, id+1, List.hd (op_mutation orig)) + ) + | IncrIntCst n -> + if n+1 >= IntSet.cardinal !records.consts then + DecrIntCst 0 + else IncrIntCst (n+1) + | DecrIntCst n -> + if n+1 >= IntSet.cardinal !records.consts then + SwitchIntCst (0, 0) + else DecrIntCst (n+1) + | SwitchIntCst (n, m) -> + if m+1 > -1 + IntSet.cardinal !records.consts then + SwitchIntCst (n, m+1) + else if n+1 >= IntSet.cardinal !records.consts then + SwitchIntCst (n+1, 0) + else first_boolexpr () + + in + (* Format.eprintf "from: %a to: %a@." print_directive m print_directive res; *) + res + +let fold_mutate nb prog = + incr random_seed; + Random.init !random_seed; + let find_next_new mutants mutant = + let rec find_next_new init current = + if init = current then raise Not_found else + if List.mem current mutants then + find_next_new init (next_change current) + else + current + in + find_next_new mutant (next_change mutant) + in + (* Creating list of nb elements of mutants *) + let rec create_mutants_directives rnb mutants = + if rnb <= 0 then mutants + else + let random_mutation = + match Random.int 6 with + | 5 -> IncrIntCst (try Random.int (IntSet.cardinal !records.consts) with _ -> 0) + | 4 -> DecrIntCst (try Random.int (IntSet.cardinal !records.consts) with _ -> 0) + | 3 -> SwitchIntCst ((try Random.int (IntSet.cardinal !records.consts) with _ -> 0), (try Random.int (-1 + IntSet.cardinal !records.consts) with _ -> 0)) + | 2 -> Pre (try Random.int !records.nb_pre with _ -> 0) + | 1 -> Boolexpr (try Random.int !records.nb_boolexpr with _ -> 0) + | 0 -> let bindings = OpCount.bindings !records.nb_op in + let op, nb_op = List.nth bindings (try Random.int (List.length bindings) with _ -> 0) in + let new_op = List.nth (op_mutation op) (try Random.int (List.length (op_mutation op)) with _ -> 0) in + Op (op, (try Random.int nb_op with _ -> 0), new_op) + | _ -> assert false + in + if List.mem random_mutation mutants then + try + let new_mutant = (find_next_new mutants random_mutation) in + report ~level:2 (fun fmt -> fprintf fmt " %i mutants generated out of %i expected@." (nb-rnb) nb); + create_mutants_directives (rnb-1) (new_mutant::mutants) + with Not_found -> ( + report ~level:1 (fun fmt -> fprintf fmt "Only %i mutants generated out of %i expected@." (nb-rnb) nb); + mutants + ) + else + create_mutants_directives (rnb-1) (random_mutation::mutants) + in + let mutants_directives = create_mutants_directives nb [] in + List.map (fun d -> d, create_mutant prog d) mutants_directives + + +let mutate nb prog = + records := compute_records prog; + (* Format.printf "Records: %i pre, %i boolexpr" (\* , %a ops *\) *) + (* !records.nb_pre *) +(* !records.nb_boolexpr *) +(* (\* !records.op *\) *) +(* ; *) + fold_mutate nb prog, print_directive + + + + +(* Local Variables: *) +(* compile-command:"make -C .." *) +(* End: *) + + diff --git a/src/options.ml b/src/options.ml index 42d00716..0e1fd67a 100755 --- a/src/options.ml +++ b/src/options.ml @@ -22,7 +22,7 @@ else Version.include_path let print_version () = Format.printf "Lustrec compiler, version %s (%s)@." version codename; Format.printf "Include directory: %s@." include_path; - Format.printf "User selected Include directory: %s@." !include_dir + Format.printf "User selected include directory: %s@." !include_dir let main_node = ref "" let static_mem = ref true @@ -53,6 +53,12 @@ let salsa_enabled = ref true let sfunction = ref "" +(* test generation options *) +let nb_mutants = ref 1000 +let gen_mcdc = ref false +let no_mutation_suffix = ref false + + let set_mpfr prec = if prec > 0 then ( mpfr := true; @@ -61,12 +67,20 @@ let set_mpfr prec = ) else failwith "mpfr requires a positive integer" - -let options = -[ "-d", Arg.Set_string dest_dir, -"uses the specified directory \x1b[4mdir\x1b[0m as root for generated/imported object and C files <default: .>"; -"-I", Arg.Set_string include_dir, "Include directory"; + +let common_options = + [ "-d", Arg.Set_string dest_dir, "uses the specified \x1b[4mdirectory\x1b[0m as root for generated/imported object and C files <default: .>"; + "-I", Arg.Set_string include_dir, "sets include \x1b[4mdirectory\x1b[0m"; "-node", Arg.Set_string main_node, "specifies the \x1b[4mmain\x1b[0m node"; + "-print-types", Arg.Set print_types, "prints node types"; + "-print-clocks", Arg.Set print_clocks, "prints node clocks"; + "-verbose", Arg.Set_int verbose_level, "changes verbose \x1b[4mlevel\x1b[0m <default: 1>"; + "-version", Arg.Unit print_version, " displays the version"; + ] + +let lustrec_options = + common_options @ + [ "-init", Arg.Set delay_calculus, "performs an initialisation analysis for Lustre nodes <default: no analysis>"; "-dynamic", Arg.Clear static_mem, "specifies a dynamic allocation scheme for main Lustre node <default: static>"; "-check-access", Arg.Set check, "checks at runtime that array accesses always lie within bounds <default: no check>"; @@ -77,20 +91,25 @@ let options = "-c-spec", Arg.Unit (fun () -> spec := "c"), "generates a C encoding of the specification instead of ACSL contracts and annotations. Only meaningful for the C backend"; "-java", Arg.Unit (fun () -> output := "java"), "generates Java output instead of C"; "-horn", Arg.Unit (fun () -> output := "horn"), "generates Horn clauses encoding output instead of C"; - "-horn-traces", Arg.Unit (fun () -> output := "horn"; traces:=true), "produce traceability file for Horn backend. Enable the horn backend."; - "-horn-cex", Arg.Unit (fun () -> output := "horn"; horn_cex:=true), "generate cex enumeration. Enable the horn backend (work in progress)"; - "-horn-query", Arg.Unit (fun () -> output := "horn"; horn_query:=true), "generate queries in generated Horn file. Enable the horn backend (work in progress)"; - "-horn-sfunction", Arg.Set_string sfunction, "Get the endpoint predicate of the sfunction"; - "-print_reuse", Arg.Set print_reuse, "prints variable reuse policy"; + "-horn-traces", Arg.Unit (fun () -> output := "horn"; traces:=true), "produces traceability file for Horn backend. Enable the horn backend."; + "-horn-cex", Arg.Unit (fun () -> output := "horn"; horn_cex:=true), "generates cex enumeration. Enable the horn backend (work in progress)"; + "-horn-query", Arg.Unit (fun () -> output := "horn"; horn_query:=true), "generates queries in generated Horn file. Enable the horn backend (work in progress)"; + "-horn-sfunction", Arg.Set_string sfunction, "gets the endpoint predicate of the \x1b[4msfunction\x1b[0m"; + "-print-reuse", Arg.Set print_reuse, "prints variable reuse policy"; "-lustre", Arg.Unit (fun () -> output := "lustre"), "generates Lustre output, performing all active optimizations"; - "-inline", Arg.Unit (fun () -> global_inline := true; const_unfold := true), "inline all node calls (require a main node). Implies constant unfolding"; - "-witnesses", Arg.Set witnesses, "enable production of witnesses during compilation"; - "-print_types", Arg.Set print_types, "prints node types"; - "-print_clocks", Arg.Set print_clocks, "prints node clocks"; + "-inline", Arg.Unit (fun () -> global_inline := true; const_unfold := true), "inlines all node calls (require a main node). Implies constant unfolding"; + "-witnesses", Arg.Set witnesses, "enables production of witnesses during compilation"; "-O", Arg.Set_int optimization, "changes optimization \x1b[4mlevel\x1b[0m <default: 2>"; "-verbose", Arg.Set_int verbose_level, "changes verbose \x1b[4mlevel\x1b[0m <default: 1>"; - "-version", Arg.Unit print_version, " displays the version";] + "-version", Arg.Unit print_version, " displays the version"; + ] +let lustret_options = + common_options @ + [ "-nb-mutants", Arg.Set_int nb_mutants, "\x1b[4mnumber\x1b[0m of mutants to produce <default: 1000>"; + "-mcdc-cond", Arg.Set gen_mcdc, "generates MC/DC coverage"; + "-no-mutation-suffix", Arg.Set no_mutation_suffix, "does not rename node with the _mutant suffix" + ] let plugin_opt (name, activate, options) = ( "-" ^ name , Arg.Unit activate, "activate plugin " ^ name ) :: diff --git a/src/pathConditions.ml b/src/pathConditions.ml new file mode 100644 index 00000000..13330125 --- /dev/null +++ b/src/pathConditions.ml @@ -0,0 +1,162 @@ +open LustreSpec +open Corelang +open Log +open Format + +module IdSet = Set.Make (struct type t = expr * int let compare = compare end) + +let inout_vars = ref [] + +let print_tautology_var fmt v = + match (Types.repr v.var_type).Types.tdesc with + | Types.Tbool -> Format.fprintf fmt "(%s or not %s)" v.var_id v.var_id + | Types.Tint -> Format.fprintf fmt "(%s > 0 or %s <= 0)" v.var_id v.var_id + | Types.Treal -> Format.fprintf fmt "(%s > 0 or %s <= 0)" v.var_id v.var_id + | _ -> Format.fprintf fmt "(true)" + +let print_path arg = match !inout_vars with + | [] -> Format.printf "%t@." arg + | l -> Format.printf "%t and %a@." arg (Utils.fprintf_list ~sep:" and " (fun fmt elem -> print_tautology_var fmt elem)) l + +let rel_op = ["="; "!="; "<"; "<="; ">" ; ">=" ] + +let rec print_pre fmt nb_pre = + if nb_pre <= 0 then () + else ( + Format.fprintf fmt "pre "; + print_pre fmt (nb_pre-1) + ) +(* +let combine2 f sub1 sub2 = + let elem_e1 = List.fold_right IdSet.add (List.map fst sub1) IdSet.empty in + let elem_e2 = List.fold_right IdSet.add (List.map fst sub2) IdSet.empty in + let common = IdSet.inter elem_e1 elem_e2 in + let sub1_filtered = List.filter (fun (v, _) -> not (IdSet.mem v common)) sub1 in + let sub2_filtered = List.filter (fun (v, _) -> not (IdSet.mem v common)) sub2 in + (List.map (fun (v, negv) -> (v, f negv e2)) sub1_filtered) @ + (List.map (fun (v, negv) -> (v, f e1 negv)) sub2_filtered) @ + (List.map (fun v -> (v, {expr with expr_desc = Expr_arrow(List.assoc v sub1, List.assoc v sub2)}) (IdSet.elements common)) ) +*) + +let rec select (v: expr * int) (active: bool list) (modified: ((expr * int) * expr) list list) (orig: expr list) = +match active, modified, orig with +| true::active_tl, e::modified_tl, _::orig_tl -> (List.assoc v e)::(select v active_tl modified_tl orig_tl) +| false::active_tl, _::modified_tl, e::orig_tl -> e::(select v active_tl modified_tl orig_tl) +| [], [], [] -> [] +| _ -> assert false + +let combine (f: expr list -> expr ) subs orig : ((expr * int) * expr) list = + let elems = List.map (fun sub_i -> List.fold_right IdSet.add (List.map fst sub_i) IdSet.empty) subs in + let all = List.fold_right IdSet.union elems IdSet.empty in + List.map (fun v -> + let active_subs = List.map (IdSet.mem v) elems in + v, f (select v active_subs subs orig) + ) (IdSet.elements all) + +let rec compute_neg_expr cpt_pre expr = + match expr.expr_desc with + | Expr_tuple l -> + let neg = List.map (compute_neg_expr cpt_pre) l in + combine (fun l' -> {expr with expr_desc = Expr_tuple l'}) neg l + + | Expr_ite (i,t,e) when (Types.repr t.expr_type).Types.tdesc = Types.Tbool -> + let list = [i; t; e] in + let neg = List.map (compute_neg_expr cpt_pre) list in + combine (fun [i'; t'; e'] -> {expr with expr_desc = Expr_ite(i', t', e')}) neg list + | Expr_ite (i,t,e) -> ( (* We return the guard as a new guard *) + gen_mcdc_cond_guard i; + let list = [i; t; e] in + let neg = List.map (compute_neg_expr cpt_pre) list in + combine (fun [i'; t'; e'] -> {expr with expr_desc = Expr_ite(i', t', e')}) neg list + ) + | Expr_arrow (e1, e2) -> + let e1' = compute_neg_expr cpt_pre e1 in + let e2' = compute_neg_expr cpt_pre e2 in + combine (fun [x;y] -> { expr with expr_desc = Expr_arrow (x, y) }) [e1'; e2'] [e1; e2] + | Expr_pre e -> + List.map + (fun (v, negv) -> (v, { expr with expr_desc = Expr_pre negv } )) + (compute_neg_expr (cpt_pre+1) e) + + | Expr_appl (op_name, args, r) when List.mem op_name rel_op -> + [(expr, cpt_pre), mkpredef_unary_call Location.dummy_loc "not" expr] + + | Expr_appl (op_name, args, r) -> + List.map + (fun (v, negv) -> (v, { expr with expr_desc = Expr_appl (op_name, negv, r) } )) + (compute_neg_expr cpt_pre args) + + | Expr_ident _ when (Types.repr expr.expr_type).Types.tdesc = Types.Tbool -> + [(expr, cpt_pre), mkpredef_unary_call Location.dummy_loc "not" expr] + | _ -> [] + +and + gen_mcdc_cond_var v expr = + report ~level:1 (fun fmt -> Format.fprintf fmt ".. Generating MC/DC cond for boolean flow %s and expression %a@." v Printers.pp_expr expr); + let leafs_n_neg_expr = compute_neg_expr 0 expr in + if List.length leafs_n_neg_expr > 1 then ( + List.iter (fun ((vi, nb_pre), expr_neg_vi) -> + print_path (fun fmt -> Format.fprintf fmt "%a%a and (%s != %a)" print_pre nb_pre Printers.pp_expr vi v Printers.pp_expr expr_neg_vi); + print_path (fun fmt -> Format.fprintf fmt "(not %a%a) and (%s != %a)" print_pre nb_pre Printers.pp_expr vi v Printers.pp_expr expr_neg_vi) + ) leafs_n_neg_expr + ) + +and gen_mcdc_cond_guard expr = + report ~level:1 (fun fmt -> Format.fprintf fmt".. Generating MC/DC cond for guard %a@." Printers.pp_expr expr); + let leafs_n_neg_expr = compute_neg_expr 0 expr in + if List.length leafs_n_neg_expr > 1 then ( + List.iter (fun ((vi, nb_pre), expr_neg_vi) -> + print_path (fun fmt -> Format.fprintf fmt "%a%a and (%a != %a)" print_pre nb_pre Printers.pp_expr vi Printers.pp_expr expr Printers.pp_expr expr_neg_vi); + print_path (fun fmt -> Format.fprintf fmt "(not %a%a) and (%a != %a)" print_pre nb_pre Printers.pp_expr vi Printers.pp_expr expr Printers.pp_expr expr_neg_vi) + + ) leafs_n_neg_expr + ) + + +let rec mcdc_expr cpt_pre expr = + match expr.expr_desc with + | Expr_tuple l -> List.iter (mcdc_expr cpt_pre) l + | Expr_ite (i,t,e) -> (gen_mcdc_cond_guard i; List.iter (mcdc_expr cpt_pre) [t; e]) + | Expr_arrow (e1, e2) -> List.iter (mcdc_expr cpt_pre) [e1; e2] + | Expr_pre e -> mcdc_expr (cpt_pre+1) e + | Expr_appl (_, args, _) -> mcdc_expr cpt_pre args + | _ -> () + +let mcdc_var_def v expr = + match (Types.repr expr.expr_type).Types.tdesc with + | Types.Tbool -> gen_mcdc_cond_var v expr + | _ -> mcdc_expr 0 expr + +let mcdc_node_eq eq = + match eq.eq_lhs, (Types.repr eq.eq_rhs.expr_type).Types.tdesc, eq.eq_rhs.expr_desc with + | [lhs], Types.Tbool, _ -> gen_mcdc_cond_var lhs eq.eq_rhs + | _::_, Types.Ttuple tl, Expr_tuple rhs -> List.iter2 mcdc_var_def eq.eq_lhs rhs + | _ -> mcdc_expr 0 eq.eq_rhs + +let mcdc_top_decl td = + match td.top_decl_desc with + | Node nd -> List.iter mcdc_node_eq nd.node_eqs + | _ -> () + + +let mcdc prog = + (* If main node is provided add silly constraints to show in/out variables in the path condition *) + if !Options.main_node <> "" then ( + inout_vars := + let top = List.find + (fun td -> + match td.top_decl_desc with + | Node nd when nd.node_id = !Options.main_node -> true + | _ -> false) + prog + in + match top.top_decl_desc with + | Node nd -> nd.node_inputs @ nd.node_outputs + | _ -> assert false); + List.iter mcdc_top_decl prog + +(* Local Variables: *) +(* compile-command:"make -C .." *) +(* End: *) + + -- GitLab