diff --git a/src/analyses/mCPRegistry.ml b/src/analyses/mCPRegistry.ml index 05519dbb62..a7e444268a 100644 --- a/src/analyses/mCPRegistry.ml +++ b/src/analyses/mCPRegistry.ml @@ -49,6 +49,15 @@ let register_analysis = Hashtbl.replace registered_name n !count; incr count +let registered_simplified_analysis (module S:SimplifiedAnalysis.SimplifiedSpec) = + let module S':MCPSpec = struct + include SimplifiedLifter.FromSimplifiedSpec(S) + module A = UnitA + let access _ _ = () + end + in + register_analysis (module S') + let find_spec = Hashtbl.find registered let find_spec_name n = (find_spec n).name let find_id = Hashtbl.find registered_name diff --git a/src/analyses/tutorials/gStoreWidening.ml b/src/analyses/tutorials/gStoreWidening.ml new file mode 100644 index 0000000000..47d2678ce1 --- /dev/null +++ b/src/analyses/tutorials/gStoreWidening.ml @@ -0,0 +1,197 @@ +open GoblintCil +open SimplifiedAnalysis +open GStoreWideningHelper + +(** + There are two regression tests for this analysis, which you can run by calling: + - ./regtest.sh 99 05 + - ./regtest.sh 99 06 + + Running these scripts also produces a visualization of the analysis results as a HTML file in the folder + result. + + You can access these by spinning up a HTTP server, e.g., by calling `python3 -m http.server` + + First fix the TODO: 1) to ensure unreachable code is marked as dead. + Then, tackle TODO: 2) to change this analysis so it tracks global variables + + After modifying things, don't forget to compile by running `make` +*) + + +module Analysis: SimplifiedSpec = struct + let name = "gStoreWidening" + + module I = GStoreWideningHelper.Intervals + + module D = MapDomain.MapBot (Basetype.Variables) (I) + module C = Printable.Unit + + (** TODO: 2) Modify so that we store values for globals instead of always assuming they are top *) + module V = Printable.Unit + module G = Lattice.Unit + + let startstate = D.bot () + let startcontext = () + + (* Evaluate a single variable given a local state *) + let eval_varinfo man state v = + if v.vglob then + (** TODO: 2) Modify so that we store values for globals *) + top_of_var v + else + D.find v state + + (* evaluate an expression given a local state, can remain unmodified *) + let rec eval man (state: D.t) (e: exp) = + try + match e with + | Const (CInt (i, ik, _)) -> + const_int ik i + | Lval (Var v, NoOffset) when GStoreWideningHelper.is_tracked_var v -> + eval_varinfo man state v + | CastE (_, t, e) -> + cast_to_typ t (eval man state e) + | UnOp (Neg, e, t) -> + I.neg (cast_to_typ t (eval man state e)) + | UnOp (BNot, e, t) -> + I.lognot (cast_to_typ t (eval man state e)) + | UnOp (LNot, e, t) -> + begin match I.to_bool (eval man state e) with + | Some b -> I.of_bool (ikind_of_typ t) (not b) + | None -> top_of_typ t + end + | BinOp (op, e1, e2, t) -> + eval_binop man state op e1 e2 t + | _ -> + top_of_exp e + with + | IntDomain.ArithmeticOnIntegerBot _ + | IntDomain.IncompatibleIKinds _ + | Cilfacade.TypeOfError _ -> + top_of_exp e + + (* evaluation of binary operators, can remain unmodified *) + and eval_binop man state op e1 e2 t = + let ik = ikind_of_typ t in + let v1 = cast_to_typ t (eval man state e1) in + let v2 = cast_to_typ t (eval man state e2) in + match op with + | PlusA | PlusPI | IndexPI -> + I.add v1 v2 + | MinusA | MinusPI | MinusPP -> + I.sub v1 v2 + | Mult -> + I.mul v1 v2 + | Div -> + I.div v1 v2 + | Mod -> + I.rem v1 v2 + | BAnd -> + I.logand v1 v2 + | BOr -> + I.logor v1 v2 + | BXor -> + I.logxor v1 v2 + | Shiftlt -> + I.shift_left v1 v2 + | Shiftrt -> + I.shift_right v1 v2 + | Lt | Gt | Le | Ge | Eq | Ne -> + let cmp = + match op with + | Lt -> I.lt v1 v2 + | Gt -> I.gt v1 v2 + | Le -> I.le v1 v2 + | Ge -> I.ge v1 v2 + | Eq -> I.eq v1 v2 + | Ne -> I.ne v1 v2 + | _ -> None + in + begin match cmp with + | Some b -> I.of_bool ik b + | None -> I.top_of ik + end + | LAnd | LOr -> + begin match I.to_bool v1, I.to_bool v2 with + | Some b1, Some b2 -> + let b = if op = LAnd then b1 && b2 else b1 || b2 in + I.of_bool ik b + | _ -> I.top_of ik + end + + let query man state (type a) (q: a Queries.t): a Queries.result = + match q with + | Queries.EvalInt e -> + let ik = ikind_of_exp e in + let v = eval man state e in + begin match I.minimal v, I.maximal v with + | Some l, Some u -> Queries.ID.of_interval ik (l, u) + | _ -> Queries.Result.top q + end + | _ -> + Queries.Result.top q + + let assign man state lval rval = + match is_tracked_lval lval with + | Some v -> + if not v.vglob then + D.add v (cast_to_typ v.vtype (eval man state rval)) state + else + (** TODO: 2) Modify so that we store values for globals *) + state + | None -> + state + + (** TODO: 1) raise Analyses.Deadcode if we branch on a condition that is known-to-be false *) + (* Returns the state resulting when the expression `e` evaluates to `tv` *) + let branch man state e tv = + let e_evaluated_to_bool = I.to_bool (eval man state e) in + state + + + (* Glue code, does not need to be modified for this tutorial *) + let set_lval_top state = function + | Some (Var v, NoOffset) when is_tracked_var v && not v.vglob -> + D.add v (I.top_of (ikind_of_typ v.vtype)) state + | _ -> state + + let return _ state _ _ = + state + + let body _ state f = + List.fold_left (fun acc v -> + if is_tracked_var v then + D.add v (I.top_of (ikind_of_typ v.vtype)) acc + else + acc + ) state f.slocals + + let enter man state _ f args = + List.fold_left2 (fun acc formal actual -> + if is_tracked_var formal then + D.add formal (cast_to_typ formal.vtype (eval man state actual)) acc + else + acc + ) (D.bot ()) f.sformals args + + let combine _ state _ lval _ _ = + set_lval_top state lval + + let special man state lval _ _ = + set_lval_top state lval + + let context _ (_, c) _ _ = + c + + let threadenter _ _ f _ = + List.fold_left (fun acc v -> + if is_tracked_var v then + D.add v (I.top_of (ikind_of_typ v.vtype)) acc + else + acc + ) (D.bot ()) f.sformals +end + +let _ = + MCPRegistry.registered_simplified_analysis (module Analysis:SimplifiedSpec) diff --git a/src/analyses/tutorials/gStoreWideningHelper.ml b/src/analyses/tutorials/gStoreWideningHelper.ml new file mode 100644 index 0000000000..29bbd623e8 --- /dev/null +++ b/src/analyses/tutorials/gStoreWideningHelper.ml @@ -0,0 +1,34 @@ +(** Contains some defintions that are helpful for the tutorial but out of scope *) +open GoblintCil + +(* Complicated definition for technical reasons relating to different int types *) +module Intervals = IntDomain.IntDomWithDefaultIkind(IntDomain.IntDomLifter (IntDomain.SOverflowUnlifter (IntDomain.Interval))) (IntDomain.PtrDiffIkind) + +let is_tracked_var v = + Cil.isIntegralType v.vtype && not v.vaddrof + +let is_tracked_lval = function + | Var v, NoOffset when is_tracked_var v -> Some v + | _ -> None + +let ikind_of_typ t = + Cilfacade.get_ikind t + +let ikind_of_exp e = + Cilfacade.get_ikind_exp e + +let top_of_typ t = + Intervals.top_of (ikind_of_typ t) + +let top_of_exp e = + Intervals.top_of (ikind_of_exp e) + +let top_of_var v = + top_of_exp (Lval (Var v, NoOffset)) + + +let cast_to_typ t x = + Intervals.cast_to ~kind:Internal (ikind_of_typ t) x + +let const_int ik i = + Intervals.of_int ik i diff --git a/src/analyses/tutorials/gStoreWideningSol.ml b/src/analyses/tutorials/gStoreWideningSol.ml new file mode 100644 index 0000000000..93e749a132 --- /dev/null +++ b/src/analyses/tutorials/gStoreWideningSol.ml @@ -0,0 +1,205 @@ +open GoblintCil +open SimplifiedAnalysis +open GStoreWideningHelper + +(** + + ********************************************************************************************************************** + ** THIS IS THE SOLUTION TO THIS TUTORIAL ANALYSIS, READING THIS BEFORE DOING THE TUTORIAL WILL SPOIL THE FUN. ** + ** YOU HAVE BEEN WARNED. ** + ********************************************************************************************************************** + + There are two regression tests for this analysis, which you can run by calling: + - ./regtest.sh 99 05 + - ./regtest.sh 99 06 + + Running these scripts also produces a visualization of the analysis results as a HTML file in the folder + result. + + You can access these by spinning up a HTTP server, e.g., by calling `python3 -m http.server` + + First fix the TODO: 1) to ensure unreachable code is marked as dead. + Then, tackle TODO: 2) to change this analysis so it tracks global variables + + After modifying things, don't forget to compile by running `make` +*) + + +module Analysis: SimplifiedSpec = struct + let name = "gStoreWideningSol" + + module I = GStoreWideningHelper.Intervals + + module D = MapDomain.MapBot (Basetype.Variables) (I) + module C = Printable.Unit + + (** TODO: 2) Modify so that we store values for globals instead of always assuming they are top *) + module V = Basetype.Variables + module G = I + + let startstate = D.bot () + let startcontext = () + + (* Evaluate a single variable given a local state *) + let eval_varinfo man state v = + if v.vglob then + man.global v + else + D.find v state + + (* evaluate an expression given a local state, can remain unmodified *) + let rec eval man (state: D.t) (e: exp) = + try + match e with + | Const (CInt (i, ik, _)) -> + const_int ik i + | Lval (Var v, NoOffset) when GStoreWideningHelper.is_tracked_var v -> + eval_varinfo man state v + | CastE (_, t, e) -> + cast_to_typ t (eval man state e) + | UnOp (Neg, e, t) -> + I.neg (cast_to_typ t (eval man state e)) + | UnOp (BNot, e, t) -> + I.lognot (cast_to_typ t (eval man state e)) + | UnOp (LNot, e, t) -> + begin match I.to_bool (eval man state e) with + | Some b -> I.of_bool (ikind_of_typ t) (not b) + | None -> top_of_typ t + end + | BinOp (op, e1, e2, t) -> + eval_binop man state op e1 e2 t + | _ -> + top_of_exp e + with + | IntDomain.ArithmeticOnIntegerBot _ + | IntDomain.IncompatibleIKinds _ + | Cilfacade.TypeOfError _ -> + top_of_exp e + + (* evaluation of binary operators, can remain unmodified *) + and eval_binop man state op e1 e2 t = + let ik = ikind_of_typ t in + let v1 = cast_to_typ t (eval man state e1) in + let v2 = cast_to_typ t (eval man state e2) in + match op with + | PlusA | PlusPI | IndexPI -> + I.add v1 v2 + | MinusA | MinusPI | MinusPP -> + I.sub v1 v2 + | Mult -> + I.mul v1 v2 + | Div -> + I.div v1 v2 + | Mod -> + I.rem v1 v2 + | BAnd -> + I.logand v1 v2 + | BOr -> + I.logor v1 v2 + | BXor -> + I.logxor v1 v2 + | Shiftlt -> + I.shift_left v1 v2 + | Shiftrt -> + I.shift_right v1 v2 + | Lt | Gt | Le | Ge | Eq | Ne -> + let cmp = + match op with + | Lt -> I.lt v1 v2 + | Gt -> I.gt v1 v2 + | Le -> I.le v1 v2 + | Ge -> I.ge v1 v2 + | Eq -> I.eq v1 v2 + | Ne -> I.ne v1 v2 + | _ -> None + in + begin match cmp with + | Some b -> I.of_bool ik b + | None -> I.top_of ik + end + | LAnd | LOr -> + begin match I.to_bool v1, I.to_bool v2 with + | Some b1, Some b2 -> + let b = if op = LAnd then b1 && b2 else b1 || b2 in + I.of_bool ik b + | _ -> I.top_of ik + end + + let query man state (type a) (q: a Queries.t): a Queries.result = + match q with + | Queries.EvalInt e -> + let ik = ikind_of_exp e in + let v = eval man state e in + begin match I.minimal v, I.maximal v with + | Some l, Some u -> Queries.ID.of_interval ik (l, u) + | _ -> Queries.Result.top q + end + | _ -> + Queries.Result.top q + + let assign man state lval rval = + match is_tracked_lval lval with + | Some v -> + if not v.vglob then + D.add v (cast_to_typ v.vtype (eval man state rval)) state + else + (** TODO: 2) Modify so that we store values for globals *) + (man.sideg v (cast_to_typ v.vtype (eval man state rval)); + state) + | None -> + state + + (** TODO: 1) raise Analyses.Deadcode if we branch on a condition that is known-to-be false *) + (* Returns the state resulting when the expression `e` evaluates to `tv` *) + let branch man state e tv = + let e_evaluated_to_bool = I.to_bool (eval man state e) in + match e_evaluated_to_bool with + | Some e when e <> tv -> raise Analyses.Deadcode + | _ -> state + + + (* Glue code, does not need to be modified for this tutorial *) + let set_lval_top state = function + | Some (Var v, NoOffset) when is_tracked_var v && not v.vglob -> + D.add v (I.top_of (ikind_of_typ v.vtype)) state + | _ -> state + + let return _ state _ _ = + state + + let body _ state f = + List.fold_left (fun acc v -> + if is_tracked_var v then + D.add v (I.top_of (ikind_of_typ v.vtype)) acc + else + acc + ) state f.slocals + + let enter man state _ f args = + List.fold_left2 (fun acc formal actual -> + if is_tracked_var formal then + D.add formal (cast_to_typ formal.vtype (eval man state actual)) acc + else + acc + ) (D.bot ()) f.sformals args + + let combine _ state _ lval _ _ = + set_lval_top state lval + + let special man state lval _ _ = + set_lval_top state lval + + let context _ (_, c) _ _ = + c + + let threadenter _ _ f _ = + List.fold_left (fun acc v -> + if is_tracked_var v then + D.add v (I.top_of (ikind_of_typ v.vtype)) acc + else + acc + ) (D.bot ()) f.sformals +end + +let _ = + MCPRegistry.registered_simplified_analysis (module Analysis:SimplifiedSpec) diff --git a/src/framework/simplifiedAnalysis.ml b/src/framework/simplifiedAnalysis.ml new file mode 100644 index 0000000000..c4d5bf0102 --- /dev/null +++ b/src/framework/simplifiedAnalysis.ml @@ -0,0 +1,71 @@ +open GoblintCil +open Pretty +open Analyses + + +(** Man(ager) is passed to transfer functions and allows accessing the + the context, read values from globals, side-effect values to globals, + and query information from other analyses *) +type ('g,'c,'v) man = + { ask : 'a. 'a Queries.t -> 'a Queries.result + (* To communicate with other analyses *) + ; edge : MyCFG.edge + ; orig_node : MyCFG.node + ; dest_node : MyCFG.node + ; context : 'c + ; global : 'v -> 'g + ; sideg : 'v -> 'g -> unit + } + +module type UnknownSet = Printable.S + +module type SimplifiedSpec = sig + + module V : UnknownSet (** Set of globals. *) + + module G : Lattice.S (** Domain for globals. *) + module D : Lattice.S (** Domain for locals. *) + + module C : Printable.S (** Context information. *) + + val name : string (** Name of the analysis. *) + + val startstate : D.t (** Initial local state for main. *) + val startcontext: C.t (** Initial context for main. *) + + val query : (G.t, C.t, V.t) man -> D.t -> 'a Queries.t -> 'a Queries.result + + (** A transfer function which handles the assignment of a rval to a lval, i.e., + it handles program points of the form "lval = rval;" *) + val assign: (G.t, C.t, V.t) man -> D.t -> lval -> exp -> D.t + + (** A transfer function which handles conditional branching yielding the + truth value passed as a boolean argument *) + val branch: (G.t, C.t, V.t) man -> D.t -> exp -> bool -> D.t + + (** A transfer function which handles the return statement, i.e., + "return exp" or "return" in the passed function (fundec) *) + val return: (G.t, C.t, V.t) man -> D.t -> exp option -> fundec -> D.t + + (** A transfer function which handles going from the start node of a function (fundec) into + its function body. Meant to handle, e.g., initialization of local variables *) + val body: (G.t, C.t, V.t) man -> D.t -> fundec -> D.t + + (** For a function call "lval = f(args)" or "f(args)", + enter returns the initial state of the callee. *) + val enter : (G.t, C.t, V.t) man -> D.t -> lval option -> fundec -> exp list -> D.t + + (** Combines the states before and after the call. *) + val combine: (G.t, C.t, V.t) man -> D.t -> D.t -> lval option -> fundec -> exp list -> D.t + + (** A transfer function which, for a call to a {e special} function f "lval = f(args)" or "f(args)", + computes the caller state after the function call *) + val special : (G.t, C.t, V.t) man -> D.t -> lval option -> varinfo -> exp list -> D.t + + (** Compute the context for a function call, given the local state and context at the caller, + the called function and the local state inside the callee *) + val context: (G.t, C.t, V.t) man -> (D.t * C.t) -> fundec -> D.t -> C.t + + (** Compute the start state of a new thread starting with the function given by fundec *) + val threadenter: (G.t, C.t, V.t) man -> D.t -> fundec -> exp list -> D.t +end diff --git a/src/framework/simplifiedLifter.ml b/src/framework/simplifiedLifter.ml new file mode 100644 index 0000000000..9297d4d02e --- /dev/null +++ b/src/framework/simplifiedLifter.ml @@ -0,0 +1,103 @@ +open Analyses +open SimplifiedAnalysis + +(** Lift a {!SimplifiedAnalysis.SimplifiedSpec} to a regular {!Analyses.Spec}. + + The simplified interface keeps the current local state as an explicit + argument to transfer functions and has a single [combine] hook. This + adapter reconstructs the regular manager shape and supplies the regular + spec hooks which are identity functions for simplified analyses. *) +module FromSimplifiedSpec (S: SimplifiedSpec): Spec = +struct + module D = S.D + module G = S.G + module C = S.C + module V = + struct + include S.V + let is_write_only _ = false + end + module P = UnitP + + type marshal = unit + + let init _ = () + let finalize () = () + + let name () = S.name + + let startstate _ = S.startstate + let exitstate _ = S.startstate + let morphstate _ d = d + + let startcontext () = S.startcontext + + let simplified_context (man: (D.t, G.t, C.t, V.t) Analyses.man) = + try man.context () with + | Man_failure _ -> S.startcontext + + let conv (man: (D.t, G.t, C.t, V.t) Analyses.man): (G.t, C.t, V.t) SimplifiedAnalysis.man = + { ask = man.ask + ; edge = man.edge + ; orig_node = man.prev_node + ; dest_node = man.node + ; context = simplified_context man + ; global = man.global + ; sideg = man.sideg + } + + let context man fd d = + S.context (conv man) (man.local, simplified_context man) fd d + + let sync man _ = man.local + + let query man = + S.query (conv man) man.local + + let assign man lv e = + S.assign (conv man) man.local lv e + + let vdecl man _ = + man.local + + let branch man e tv = + S.branch (conv man) man.local e tv + + let body man fundec = + S.body (conv man) man.local fundec + + let return man e fundec = + S.return (conv man) man.local e fundec + + let asm man = + man.local + + let skip man = + man.local + + let enter man lval f args = + [man.local, S.enter (conv man) man.local lval f args] + + let special man lval f args = + S.special (conv man) man.local lval f args + + let combine_env man _ _ _ _ _ _ _ = + man.local + + let combine_assign man lval _ f args _ fd _ = + S.combine (conv man) man.local fd lval f args + + let paths_as_set man = + [man.local] + + let threadenter man ~multiple:_ _ f args = + match Cilfacade.find_varinfo_fundec f with + | fd -> [S.threadenter (conv man) man.local fd args] + | exception Not_found -> [man.local] + + let threadspawn man ~multiple:_ _ _ _ _ = + man.local + + let event man _ _ = + man.local +end diff --git a/src/goblint_lib.ml b/src/goblint_lib.ml index 92fa69c3b4..10140e0728 100644 --- a/src/goblint_lib.ml +++ b/src/goblint_lib.ml @@ -153,6 +153,13 @@ module Signs = Signs module Taint = Taint module UnitAnalysis = UnitAnalysis +module GStoreWidening = GStoreWidening +module GStoreWideningHelper = GStoreWideningHelper +module GStoreWideningSol = GStoreWideningSol + +module SimplifiedAnalysis = SimplifiedAnalysis +module SimplifiedLifter = SimplifiedLifter + (** {2 Other} *) module Assert = Assert diff --git a/tests/regression/99-tutorials/05-gstore-zero.c b/tests/regression/99-tutorials/05-gstore-zero.c new file mode 100644 index 0000000000..9fb26c26e0 --- /dev/null +++ b/tests/regression/99-tutorials/05-gstore-zero.c @@ -0,0 +1,28 @@ +// SKIP PARAM: --set ana.activated '["gStoreWidening","assert"]' +#include + +int main() { + int x; + int unknown; + + if (unknown) { + x = -5; + } else { + x = -7; + } + + // The above code branches on an uninitialized variable. + // The value of x could be either -5 or -7. + + __goblint_check(x < 0); + + if(x > 8) { + // This is unreachable + x = 10; + } + + // This assert should also hold, as the assignment to 10 is unreachable. + __goblint_check(x < 0); + + return 0; +} diff --git a/tests/regression/99-tutorials/06-gstore-thread.c b/tests/regression/99-tutorials/06-gstore-thread.c new file mode 100644 index 0000000000..76613ed67c --- /dev/null +++ b/tests/regression/99-tutorials/06-gstore-thread.c @@ -0,0 +1,42 @@ +// SKIP PARAM: --set ana.activated '["gStoreWidening","assert","base","mallocWrapper"]' --set ana.base.privatization none --enable exp.globs_are_top +// Additional analyses are activated so framework can handle thread creation +#include +#include +int global = 0; + +void* thread(void* arg) { + + if(global < 0) { + global = -58; + } else { + global = 1; + } + + return NULL; +} + +int main() { + int x = 11; + + pthread_t t; + pthread_create(&t, NULL, &thread, NULL); + + global = x*x; + + if(global > 200) { + global = -12; + } + + __goblint_check(global < 200); + __goblint_check(global >= 0); + + + pthread_join(t, NULL); + + global = 42; + + // This is out of reach here + __goblint_check(global == 42); + + return 0; +}