diff --git a/src/analyses/wp_analyses/liveness.ml b/src/analyses/wp_analyses/liveness.ml
new file mode 100644
index 0000000000..fc442d9c29
--- /dev/null
+++ b/src/analyses/wp_analyses/liveness.ml
@@ -0,0 +1,274 @@
+open GoblintCil
+open Analyses
+
+module BackwSpec : BackwAnalyses.BackwSpecSpec = functor (ForwSpec : Analyses.Spec) ->
+struct
+
+ include BackwAnalyses.DefaultBackwSpec (ForwSpec)
+ module C = ForwSpec.C
+
+ (* Adding these module definitions because the "include" of the DefaultBackwSpec is not enough*)
+ module D_forw = ForwSpec.D
+ module G_forw = ForwSpec.G
+ module V_forw = ForwSpec.V
+ module P_forw = ForwSpec.P
+ let name () = "liveness"
+
+ module G = Lattice.Unit
+ module V = EmptyV
+ module P = EmptyP
+
+ module LiveVariableSet = SetDomain.ToppedSet (Basetype.Variables) (struct let topname = "All variables" end)
+ module D = LiveVariableSet (*Set of program variables as domain*)
+
+ let startstate v = D.empty()
+ let exitstate v = D.empty()
+
+ let rec vars_from_lval (l: lval) man_forw : varinfo list =
+ let vars_written_to =
+ match l with
+ | Var v, _ -> (
+ if (Cil.isFunctionType v.vtype) then [] else [v] (*I do not want functions in the set of live variables*)
+ )
+ | Mem exp, _ -> (*If a pointer may point to a variable, these variables are live as well...*)
+ let may_point_to = Queries.AD.to_var_may (man_forw.ask (MayPointTo exp)) in
+ if may_point_to = [] then (
+ M.warn ~category:MessageCategory.Unsound "The expression %a may point to an unknown variable. This makes the analysis unsound." d_exp exp; (*UNSOUND: I do not think that this check is enough. Maybe I should just exclude analyzing programs with variables whose address is taken.*)
+ vars_from_expr exp man_forw )
+ else (
+ Logs.debug "(!) The expression %a may point to the variables %s" d_exp exp (String.concat ", " (List.map (fun v -> v.vname) may_point_to));
+ may_point_to @ vars_from_expr exp man_forw)
+ in
+
+ let vars_in_offset =
+ match l with
+ | Var _, off -> vars_from_offset off man_forw
+ | Mem _, off -> vars_from_offset off man_forw
+ in
+
+ (vars_written_to @ vars_in_offset)
+
+ and vars_from_offset (off: offset) man_forw : varinfo list =
+ match off with
+ | NoOffset -> []
+ | Field (_, off) -> vars_from_offset off man_forw (* what to do with fieldinfo?*)
+ | Index (e, off) ->
+ let vars_in_e = vars_from_expr e man_forw in
+ let vars_in_off = vars_from_offset off man_forw in
+ (match vars_in_off with
+ | [] -> []
+ | vars_in_off -> (vars_in_e @ vars_in_off))
+
+ and vars_from_expr (e: exp) man_forw : varinfo list =
+ let rec aux acc e =
+ match e with
+ | Lval v -> vars_from_lval v man_forw @ acc
+ | BinOp (_, e1, e2, _) ->
+ let acc1 = aux acc e1 in
+ aux acc1 e2
+ | UnOp (_, e1, _) -> aux acc e1
+ | SizeOfE e1 -> aux acc e1
+ | AlignOfE e1 -> aux acc e1
+ | Question (e1, e2, e3, _) ->
+ let acc1 = aux acc e1 in
+ let acc2 = aux acc1 e2 in
+ aux acc2 e3
+ | CastE (_, e1) -> aux acc e1 (*This appears to make problems when building for jobs*)
+ | AddrOf (l1) -> (match vars_from_lval l1 man_forw with
+ | [] -> acc
+ | v -> (v @ acc)
+ )
+ | _ -> acc
+
+ in
+ aux [] e
+
+ let rec assign man man_forw (lval:lval) (rval:exp) =
+ match lval with
+ | Var v, offset ->
+ if (D.mem v man.local) then ( (* Global variables are considered live when writing to them -> No, not anymore. This of course does not wark with concurrent programs, but I am already excluding those. *)
+ let rval_vars = D.of_list (vars_from_expr rval man_forw)in
+ let rval_vars = D.filter (fun v -> not (Cil.isFunctionType v.vtype)) rval_vars in (*remove variables that are just function names*)
+
+ let offset_vars = D.of_list (vars_from_offset offset man_forw) in
+ let base_live =
+ match offset with
+ | NoOffset -> D.diff man.local (D.singleton v)
+ | _ -> man.local
+ in
+
+ D.union rval_vars (D.union offset_vars base_live)
+ ) else (
+ (* let loc = M.Location.Node man.node in *)
+ (* M.warn ~loc:loc ~category:MessageCategory.Program "Unnecessary assignment to variable '%s', as it is not live at this program point." v.vname; *)
+ man.local
+ )
+ | Mem exp, off ->
+ let ad = man_forw.ask (MayPointTo exp) in
+ let lval_vars = D.of_list (vars_from_expr exp man_forw) in
+ let rval_vars = D.of_list (vars_from_expr rval man_forw) in
+ let rval_vars = D.filter (fun v -> not (Cil.isFunctionType v.vtype)) rval_vars in (*remove variables that are just function names*)
+
+ let strong_target =
+ match off, Queries.AD.to_mval ad with
+ | NoOffset, [(v, `NoOffset)] when Queries.AD.is_element (Queries.AD.Addr.of_mval (v, `NoOffset)) ad -> Some v
+ | _ -> None
+ in
+
+
+ let log_this ()=
+ Logs.debug "(!) Assignment to memory location %a with may-point-to set [?]" d_exp exp;
+ Logs.debug "Variables in the lval: %s" (String.concat ", " (List.map (fun v -> v.vname) (D.elements lval_vars)));
+ Logs.debug "Variables in the rval: %s" (String.concat ", " (List.map (fun v -> v.vname) (D.elements rval_vars)));
+ match strong_target with
+ | Some v -> Logs.debug "Strong target variable: %s" v.vname
+ | None -> Logs.debug "No strong target variable identified"
+ in
+ log_this ();
+
+ match strong_target with
+ | Some v ->
+ D.union (assign man man_forw (Var v, NoOffset) rval) lval_vars
+ | None -> D.union rval_vars (D.union lval_vars man.local)
+
+ let branch man man_forw (exp:exp) (tv:bool) =
+ (* This just randomly asks whether all loops terimante to use getg_forw utilized in man.global *)
+ (* let () =
+ match man_forw.ask(Queries.MustTermAllLoops) with
+ | true -> Logs.debug "MustTermAllLoops is TRUE"
+ | _ -> Logs.debug "MustTermAllLoops is NOT TRUE"
+ in *)
+
+ let branch_irrelevant : bool = (
+ match Queries.eval_bool (Analyses.ask_of_man man_forw) exp with
+ | `Lifted b -> tv <> b
+ | `Bot -> false
+ | `Top -> false
+ )
+ in
+ if branch_irrelevant then (D.of_list (vars_from_expr exp man_forw))
+ else D.join man.local (D.of_list (vars_from_expr exp man_forw))
+
+ let body man man_forw (f:fundec) =
+ man.local
+
+ let enter man man_forw (lval: lval option) (f:fundec) (args:exp list) =
+
+ let global_vars_in_d = D.filter (fun v -> v.vglob) man.local in
+
+ let callee_d =
+ match lval with
+ | Some (Var v, _) ->
+ if (D.mem v man.local) then (
+ (D.singleton v)
+ ) else (
+ D.empty()
+ )
+ | Some (Mem exp, _) -> D.of_list (vars_from_expr exp man_forw)
+ | None -> D.empty()
+ in
+
+ [man.local, (D.union callee_d global_vars_in_d)]
+
+
+ let combine_env man man_forw (lval:lval option) fexp (f:fundec) (args:exp list) fc au (f_ask: Queries.ask) =
+
+ (* map relevant sformals in man.local to the corresponding variables contained in the argument*)
+ let arg_formal_pairs = List.combine args f.sformals in
+ let relevant_arg_vars =
+ List.fold_left (fun acc (arg_exp, formal_var) ->
+ if D.mem formal_var au then
+ D.join acc (D.of_list(vars_from_expr arg_exp man_forw))
+ else
+ acc
+ ) (D.empty()) arg_formal_pairs
+ in
+
+ (*join relevant*)
+ D.join man.local relevant_arg_vars
+
+ let combine_assign man man_forw (lval:lval option) fexp (f:fundec) (args:exp list) fc au (f_ask: Queries.ask) =
+ match lval with
+ | None -> man.local
+ | Some l -> assign man man_forw l fexp
+
+ (** A transfer function which handles the return statement, i.e.,
+ "return exp" or "return" in the passed function (fundec) *)
+ let return man man_forw (exp: exp option) (f:fundec) : D.t =
+
+ let return_val_is_important = (not (D.is_bot man.local)) || (String.equal f.svar.vname "main") in
+
+ match exp with
+ | None -> man.local
+ | Some e -> if return_val_is_important
+ then D.of_list (vars_from_expr e man_forw) |> D.union man.local
+ else man.local
+
+
+ let special man man_forw (lval: lval option) (f:varinfo) (arglist:exp list) =
+ (* log when called *)
+ Logs.debug "(!) Called special for function %s with arguments %s" f.vname (String.concat ", " (List.map (fun e -> Pretty.sprint ~width:80 (d_exp () e)) arglist));
+
+ let desc = LibraryFunctions.find f in
+ match desc.special arglist with
+ (* Could have some special handeling of library functions here *)
+ | _ ->
+ let argvars = List.fold_left (fun acc arg -> D.union acc (D.of_list (vars_from_expr arg man_forw))) (D.empty()) arglist in
+ match lval with
+ | None -> D.union man.local argvars
+ | Some (Var v, _) -> D.union (D.diff man.local (D.singleton(v))) argvars
+ | Some (Mem exp, _) -> D.union (D.union argvars (D.of_list (vars_from_expr exp man_forw))) man.local
+
+ let threadenter man man_forw ~multiple lval f args = [man.local]
+ let threadspawn man man_forw ~multiple lval f args fman = man.local
+
+ let query man (type a) man_forw (q: a Queries.t): a Queries.result =
+
+ (* Die recursion ist nicht sauber durchdacht *)
+ let rec is_dead_assign man man_forw (lval:lval) (rval:exp) (is_dead:bool) : (D.t * bool) =
+
+ match lval with
+ | Var v, _ ->
+ Logs.debug "D.mem v man.local is %b" (D.mem v man.local);
+ Logs.debug "v.glob is %b" v.vglob;
+ if (D.mem v man.local) then ( (*I used to care whether a variable is global, I no longer do.*)
+ let rval_vars = D.of_list (vars_from_expr rval man_forw)
+ in
+ (D.union rval_vars (D.diff man.local (D.singleton v)), false)
+ )else (
+ Logs.debug "Variable '%s' is not live at this program point." v.vname;
+ (man.local, true)
+ )
+ | Mem exp, off -> (
+ Logs.debug "lval is expression";
+ let ad = man_forw.ask (MayPointTo exp) in
+ let lval_vars = D.of_list (vars_from_expr exp man_forw) in
+ let rval_vars = D.of_list (vars_from_expr rval man_forw) in
+
+ let strong_target =
+ match off, Queries.AD.to_mval ad with
+ | NoOffset, [(v, `NoOffset)] when Queries.AD.is_element (Queries.AD.Addr.of_mval (v, `NoOffset)) ad -> Some v
+ | _ -> None
+ in
+
+ match strong_target with
+ | Some v ->
+ let rec_assign_result, is_dead =
+ match (is_dead_assign man man_forw (Var v, NoOffset) rval is_dead) with
+ | (res, new_is_dead) -> res, new_is_dead
+ in
+ (D.union rec_assign_result lval_vars, is_dead)
+ | None -> ((D.union rval_vars (D.union lval_vars man.local)), is_dead)
+ )
+ in
+
+ let open Queries in
+
+ match q with
+ | IsDeadVar v -> not (D.mem v man.local)
+ | MayBeDeadAssignment lval -> (
+ Logs.debug "Checking if assignment to lval %a may be dead at node %a with local state %a" d_lval lval Node.pretty_trace man.node D.pretty man.local;
+ match is_dead_assign man man_forw lval (Const (CInt (Z.zero, IInt, None))) false with
+ | (_, is_dead) -> Logs.debug "isdead is %b" is_dead ; is_dead )
+ | _ -> Result.top q
+end
diff --git a/src/config/options.schema.json b/src/config/options.schema.json
index 02e634d9e7..1697ecc9c9 100644
--- a/src/config/options.schema.json
+++ b/src/config/options.schema.json
@@ -1298,6 +1298,13 @@
}
},
"additionalProperties": false
+ },
+ "wp_run": {
+ "title": "ana.wp_run",
+ "description":
+ "Do a wp analysis, in this case the liveness analysis.",
+ "type": "boolean",
+ "default": false
}
},
"additionalProperties": false
diff --git a/src/domains/queries.ml b/src/domains/queries.ml
index 31e93dd0b2..ad36fad1ff 100644
--- a/src/domains/queries.ml
+++ b/src/domains/queries.ml
@@ -146,6 +146,8 @@ type _ t =
| YamlEntryGlobal: Obj.t * YamlWitnessType.Task.t -> YS.t t (** YAML witness entries for a global unknown ([Obj.t] represents [Spec.V.t]) and YAML witness task. *)
| GhostVarAvailable: WitnessGhostVar.t -> MayBool.t t
| InvariantGlobalNodes: NS.t t (** Nodes where YAML witness flow-insensitive invariants should be emitted as location invariants (if [witness.invariant.flow_insensitive-as] is configured to do so). *) (* [Spec.V.t] argument (as [Obj.t]) could be added, if this should be different for different flow-insensitive invariants. *)
+ | IsDeadVar: varinfo -> MayBool.t t (* Whether a variable is dead at a program point, i.e., not read afterwards. *)
+ | MayBeDeadAssignment: lval -> MayBool.t t (* Whether an assignment is dead, i.e., the assigned variable is not read afterwards. *)
type 'a result = 'a
@@ -221,6 +223,8 @@ struct
| YamlEntryGlobal _ -> (module YS)
| GhostVarAvailable _ -> (module MayBool)
| InvariantGlobalNodes -> (module NS)
+ | IsDeadVar _ -> (module MayBool)
+ | MayBeDeadAssignment _ -> (module MayBool)
(** Get bottom result for query. *)
let bot (type a) (q: a t): a result =
@@ -295,6 +299,8 @@ struct
| YamlEntryGlobal _ -> YS.top ()
| GhostVarAvailable _ -> MayBool.top ()
| InvariantGlobalNodes -> NS.top ()
+ | IsDeadVar _ -> MayBool.top ()
+ | MayBeDeadAssignment _ -> MayBool.top ()
end
(* The type any_query can't be directly defined in Any as t,
@@ -366,6 +372,8 @@ struct
| Any (MustProtectingLocks _) -> 61
| Any (GhostVarAvailable _) -> 62
| Any InvariantGlobalNodes -> 63
+ | Any (IsDeadVar _) -> 64
+ | Any (MayBeDeadAssignment _) -> 65
let rec compare a b =
let r = Stdlib.compare (order a) (order b) in
@@ -540,6 +548,8 @@ struct
| Any (GasExhausted f) -> Pretty.dprintf "GasExhausted %a" CilType.Fundec.pretty f
| Any (GhostVarAvailable v) -> Pretty.dprintf "GhostVarAvailable %a" WitnessGhostVar.pretty v
| Any InvariantGlobalNodes -> Pretty.dprintf "InvariantGlobalNodes"
+ | Any (IsDeadVar v) -> Pretty.dprintf "IsDeadVar %a" CilType.Varinfo.pretty v
+ | Any (MayBeDeadAssignment s) -> Pretty.dprintf "MayBeDeadAssignment %a" CilType.Lval.pretty s
end
let to_value_domain_ask (ask: ask) =
diff --git a/src/framework/analyses.ml b/src/framework/analyses.ml
index f655d89316..22db9d4031 100644
--- a/src/framework/analyses.ml
+++ b/src/framework/analyses.ml
@@ -429,9 +429,9 @@ module type SpecSys =
sig
module Spec: Spec
module EQSys: Goblint_constraint.ConstrSys.DemandGlobConstrSys with module LVar = VarF (Spec.C)
- and module GVar = GVarF (Spec.V)
- and module D = Spec.D
- and module G = GVarG (Spec.G) (Spec.C)
+ and module GVar = GVarF (Spec.V)
+ and module D = Spec.D
+ and module G = GVarG (Spec.G) (Spec.C)
module LHT: BatHashtbl.S with type key = EQSys.LVar.t
module GHT: BatHashtbl.S with type key = EQSys.GVar.t
end
@@ -443,4 +443,4 @@ sig
val gh: EQSys.G.t GHT.t
val lh: SpecSys.Spec.D.t LHT.t (* explicit SpecSys to avoid spurious module cycle *)
-end
+end
\ No newline at end of file
diff --git a/src/framework/backwAnalyses.ml b/src/framework/backwAnalyses.ml
new file mode 100644
index 0000000000..bfa83a4cbb
--- /dev/null
+++ b/src/framework/backwAnalyses.ml
@@ -0,0 +1,167 @@
+open GoblintCil
+open Analyses
+
+module M = Messages
+
+module type BackwSpec =
+sig
+ module D : Lattice.S
+ module G : Lattice.S
+ module C : Printable.S
+ module V: SpecSysVar (** Global constraint variables. *)
+ module P: DisjointDomain.Representative with type elt := D.t (** Path-representative. *)
+
+ module D_forw: Lattice.S
+ module G_forw: Lattice.S
+ module V_forw: SpecSysVar (** Global constraint variables. *)
+ module P_forw: DisjointDomain.Representative with type elt := D_forw.t (* Path-representative. *)
+ val name : unit -> string
+
+ (** Auxiliary data (outside of solution domains) that needs to be marshaled and unmarshaled.
+ This includes:
+ * hashtables,
+ * varinfos (create_var),
+ * RichVarinfos. *)
+ type marshal
+
+ (** Initialize using unmarshaled auxiliary data (if present). *)
+ val init : marshal option -> unit
+
+ (** Finalize and return auxiliary data to be marshaled. *)
+ val finalize : unit -> marshal
+ (* val finalize : G.t -> unit *)
+
+ val startstate : varinfo -> D.t
+ val morphstate : varinfo -> D.t -> D.t
+ val exitstate : varinfo -> D.t
+
+ val context: (D_forw.t, G_forw.t, C.t, V_forw.t) man -> fundec -> D_forw.t -> C.t
+ val startcontext: unit -> C.t
+
+ val sync : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> [`Normal | `Join | `JoinCall of CilType.Fundec.t | `Return] -> D.t
+ val query : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> '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: (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> lval -> exp -> D.t
+
+ (** A transfer function used for declaring local variables.
+ By default only for variable-length arrays (VLAs). *)
+ val vdecl : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> varinfo -> D.t
+
+ (** A transfer function which handles conditional branching yielding the
+ truth value passed as a boolean argument *)
+ val branch: (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> exp -> bool -> 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 : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> fundec -> D.t
+
+ (** A transfer function which handles the return statement, i.e.,
+ "return exp" or "return" in the passed function (fundec) *)
+ val return: (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> exp option -> fundec -> D.t
+
+ (** A transfer function meant to handle inline assembler program points *)
+ val asm : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> D.t
+
+ (** A transfer function which works as the identity function, i.e., it skips and does nothing.
+ Used for empty loops. *)
+ val skip : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> 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 : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> lval option -> varinfo -> exp list -> D.t
+
+ (** For a function call "lval = f(args)" or "f(args)",
+ [enter] returns a caller state, and the initial state of the callee.
+ In [enter], the caller state can usually be returned unchanged, as [combine_env] and [combine_assign] (below)
+ will compute the caller state after the function call, given the return state of the callee *)
+ val enter : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> lval option -> fundec -> exp list -> (D.t * D.t) list
+
+ (* Combine is split into two steps: *)
+
+ (** Combine environment (global variables, mutexes, etc)
+ between local state (first component from enter) and function return.
+
+ This shouldn't yet assign to the lval. *)
+ val combine_env : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> lval option -> exp -> fundec -> exp list -> C.t option -> D.t -> Queries.ask -> D.t
+
+ (** Combine return value assignment
+ to local state (result from combine_env) and function return.
+
+ This should only assign to the lval. *)
+ val combine_assign : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> lval option -> exp -> fundec -> exp list -> C.t option -> D.t -> Queries.ask -> D.t
+
+ (* Paths as sets: I know this is ugly! *)
+ val paths_as_set : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> D.t list
+
+ (** Returns initial state for created thread. *)
+ val threadenter : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> multiple:bool -> lval option -> varinfo -> exp list -> D.t list
+
+ (** Updates the local state of the creator thread using initial state of created thread. *)
+ val threadspawn : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> multiple:bool -> lval option -> varinfo -> exp list -> (D.t, G.t, C.t, V.t) man -> D.t
+
+ val event : (D.t, G.t, C.t, V.t) man -> (D_forw.t, G_forw.t, C.t, V_forw.t) man -> Events.t -> (D.t, G.t, C.t, V.t) man -> D.t
+end
+
+module type BackwSpecSpec = functor (ForwSpec : Analyses.Spec) -> sig
+ include BackwSpec
+ with module C = ForwSpec.C
+ with module D_forw = ForwSpec.D
+ with module G_forw = ForwSpec.G
+ with module V_forw = ForwSpec.V
+ with module P_forw = ForwSpec.P
+end
+
+module DefaultBackwSpec (ForwSpec : Analyses.Spec) =
+struct
+ module G = Lattice.Unit
+ module C = ForwSpec.C
+ module V = EmptyV
+ module P = EmptyP
+
+ module D_forw: Lattice.S = ForwSpec.D
+ module G_forw: Lattice.S = ForwSpec.G
+ module V_forw: SpecSysVar = ForwSpec.V (** Global constraint variables. *)
+ module P_forw: DisjointDomain.Representative with type elt := ForwSpec.D.t = ForwSpec.P (*Path-representative.*)
+
+ type marshal = unit
+ let init _ = ()
+
+ (* This means it does not matter which Spec's context function we use in control and BidirFromSpec.
+ * For understandability in other parts of the code the context-function of the forward spec should be used explicitely*)
+
+ let context = ForwSpec.context
+ let startcontext _ = ForwSpec.startcontext ()
+ let finalize () = ()
+ (* no inits nor finalize -- only analyses like Mutex, Base, ... need
+ these to do postprocessing or other imperative hacks. *)
+
+ let vdecl man _ _ = man.local
+
+ let asm x _ =
+ M.msg_final Info ~category:Unsound "ASM ignored";
+ M.info ~category:Unsound "ASM statement ignored.";
+ x.local (* Just ignore. *)
+
+ let skip x _ = x.local (* Just ignore. *)
+
+ let query _ _ (type a) (q: a Queries.t) = Queries.Result.top q
+ (* Don't know anything --- most will want to redefine this. *)
+
+ let event man _ _ _ = man.local
+
+ let morphstate v d = d
+ (* Only for those who track thread IDs. *)
+
+ let sync man _ _ = man.local
+ (* Most domains do not have a global part. *)
+
+ (* let context man _ fd x = x *)
+ (* Everything is context sensitive --- override in MCP and maybe elsewhere*)
+
+ let paths_as_set man _ = [man.local]
+
+ (* module A = UnitA *)
+ (* let access _ _ = () *)
+end
diff --git a/src/framework/bidirConstrains.ml b/src/framework/bidirConstrains.ml
new file mode 100644
index 0000000000..3c3cf17938
--- /dev/null
+++ b/src/framework/bidirConstrains.ml
@@ -0,0 +1,697 @@
+open Batteries
+open GoblintCil
+open MyCFG
+open Analyses
+open BackwAnalyses
+open Goblint_constraint.ConstrSys
+open GobConfig
+
+module type Increment =
+sig
+ val increment: increment_data option
+end
+
+module GVarF2 (V_forw: SpecSysVar) (V_backw : SpecSysVar) :
+sig
+ module GV_forw : module type of GVarF (V_forw)
+ module GV_backw : module type of GVarF (V_backw)
+ include VarType with type t = [ `Forw of GV_forw.t | `Backw of GV_backw.t ]
+ include SpecSysVar with type t := t
+ val spec : [ `Forw of V_forw.t | `Backw of V_backw.t ] -> [ `Forw of [`Left of V_forw.t] | `Backw of [`Left of V_backw.t] ]
+ val contexts : [ `Forw of V_forw.t | `Backw of V_backw.t ] -> [`Forw of [`Right of V_forw.t] | `Backw of [`Right of V_backw.t]]
+end
+=
+struct
+ module GV_forw = GVarF (V_forw)
+ module GV_backw = GVarF (V_backw)
+ type t = [ `Forw of GV_forw.t | `Backw of GV_backw.t ] [@@deriving eq, ord, hash]
+ let name () = "BidirFromSpec"
+
+ let tag _ = failwith "Std: no tag"
+
+ let relift = function
+ | `Forw x -> `Forw (GV_forw.relift x)
+ | `Backw x -> `Backw (GV_backw.relift x)
+
+ let pretty_trace () = function
+ | `Forw a -> GoblintCil.Pretty.dprintf "G_forw:%a" GV_forw.pretty_trace a
+ | `Backw a -> GoblintCil.Pretty.dprintf "G_backw:%a" GV_backw.pretty_trace a
+
+ let printXml f = function
+ | `Forw a -> GV_forw.printXml f a
+ | `Backw a -> GV_backw.printXml f a
+
+ let node = function
+ | `Forw a -> GV_forw.node a
+ | `Backw a -> GV_backw.node a
+
+ let is_write_only = function
+ | `Forw a -> GV_forw.is_write_only a
+ | `Backw a -> GV_backw.is_write_only a
+
+ let show = function
+ | `Forw a -> GV_forw.show a
+ | `Backw a -> GV_backw.show a
+
+ let pretty () = function
+ | `Forw a -> GV_forw.pretty () a
+ | `Backw a -> GV_backw.pretty () a
+ let to_yojson = function
+ | `Forw a -> GV_forw.to_yojson a
+ | `Backw a -> GV_backw.to_yojson a
+
+ let spec : [ `Forw of V_forw.t | `Backw of V_backw.t ] -> [ `Forw of [`Left of V_forw.t] | `Backw of [`Left of V_backw.t] ] = function
+ | `Forw v -> `Forw (GV_forw.spec v )
+ | `Backw v -> `Backw (GV_backw.spec v )
+
+ let contexts : [ `Forw of V_forw.t | `Backw of V_backw.t ] -> [`Forw of [`Right of V_forw.t] | `Backw of [`Right of V_backw.t]] = function
+ | `Forw v -> `Forw (GV_forw.contexts v)
+ | `Backw v -> `Backw (GV_backw.contexts v)
+
+ let var_id = show
+
+ let arbitrary () =
+ failwith "no arbitrary"
+end
+
+module BidirFromSpec (S_forw:Spec) (S_backw:BackwSpec with type D_forw.t = S_forw.D.t and type G_forw.t = S_forw.G.t and type C.t = S_forw.C.t and type V_forw.t = S_forw.V.t) (Cfg:CfgBidir) (I:Increment)
+ : sig
+ module LVar : Goblint_constraint.ConstrSys.VarType with type t = [ `L_forw of VarF(S_forw.C).t | `L_backw of VarF(S_forw.C).t ]
+ module GVar : (module type of GVarF2(S_forw.V)(S_backw.V))
+ include DemandGlobConstrSys with module LVar := LVar
+ and module GVar := GVar
+ and module D = Lattice.Lift2(S_forw.D)(S_backw.D)
+ and module G = GVarG (Lattice.Lift2(S_forw.G)(S_backw.G)) (S_forw.C)
+ end
+=
+struct
+ module LV = VarF (S_forw.C)
+ module LVar =
+ struct
+ type t = [ `L_forw of LV.t | `L_backw of LV.t ] [@@deriving eq, ord, hash]
+
+ let relift = function
+ | `L_forw x -> `L_forw (LV.relift x)
+ | `L_backw x -> `L_backw (LV.relift x)
+
+ let pretty_trace () = function
+ | `L_forw a -> GoblintCil.Pretty.dprintf "L_forw:%a" LV.pretty_trace a
+ | `L_backw a -> GoblintCil.Pretty.dprintf "L_backw:%a" LV.pretty_trace a
+
+ let printXml f = function
+ | `L_forw a -> LV.printXml f a
+ | `L_backw a -> LV.printXml f a
+
+ let var_id = function
+ | `L_forw a -> LV.var_id a
+ | `L_backw a -> LV.var_id a
+
+ let node = function
+ | `L_forw a -> LV.node a
+ | `L_backw a -> LV.node a
+
+ let is_write_only = function
+ | `L_forw a -> LV.is_write_only a
+ | `L_backw a -> LV.is_write_only a
+ end
+
+ module D = Lattice.Lift2(S_forw.D)(S_backw.D)
+ module GVar = GVarF2(S_forw.V)(S_backw.V)
+
+ module G_forw = GVarG (S_forw.G) (S_forw.C)
+ module G_backw = GVarG (S_backw.G) (S_forw.C)
+ module G = GVarG (Lattice.Lift2(S_forw.G)(S_backw.G)) (S_forw.C)
+
+ module Forward = Constraints.FromSpec(S_forw)(Cfg)(I)
+
+ (* Lowering functions for local values.*)
+ let to_forw_d (d: D.t) : S_forw.D.t =
+ match d with
+ | `Lifted1 d -> d
+ | `Bot -> S_forw.D.bot ()
+ | `Top -> S_forw.D.top ()
+ | `Lifted2 _ -> failwith "bidirConstrains: forward local has backward value"
+
+ let to_backw_d (d: D.t) : S_backw.D.t =
+ match d with
+ | `Lifted2 d -> d
+ | `Bot -> S_backw.D.bot ()
+ | `Top -> S_backw.D.top ()
+ | `Lifted1 _ -> failwith "bidirConstrains: backward local has forward value"
+
+ (* Lowering and lifting functions to deal with different global values. This is convoluted -- but tbh, it is not that much worse than the G module in the existing forwards analysis.
+ * The conversion between the CSets is quite disgusting though. *)
+
+ let to_forw_g (g: G.t) : Forward.G.t =
+ match g with
+ | `Lifted1 (`Lifted1 g) -> `Lifted1 g
+ | `Lifted1 `Bot -> `Bot
+ | `Lifted1 `Top -> `Top
+ | `Lifted1 (`Lifted2 _) -> failwith "bidirConstrains: forward global got backward value"
+ | `Lifted2 c -> `Lifted2 (G_forw.CSet.of_list (G.CSet.elements c))
+ | `Bot -> `Bot
+ | `Top -> `Top
+
+ let to_backw_g (g: G.t) : G_backw.t =
+ match g with
+ | `Lifted1 (`Lifted2 g) -> `Lifted1 g
+ | `Lifted1 `Bot -> `Bot
+ | `Lifted1 `Top -> `Top
+ | `Lifted1 (`Lifted1 _) -> failwith "bidirConstrains: backward global got forward value"
+ | `Lifted2 c -> `Lifted2 (G_backw.CSet.of_list (G.CSet.elements c))
+ | `Bot -> `Bot
+ | `Top -> `Top
+
+ let of_forw_g (g: Forward.G.t) : G.t =
+ match g with
+ | `Lifted1 g -> `Lifted1 (`Lifted1 g)
+ | `Lifted2 c -> `Lifted2 (G.CSet.of_list (G_forw.CSet.elements c))
+ | `Bot -> `Bot
+ | `Top -> `Top
+
+ let of_backw_g (g: G_backw.t) : G.t =
+ match g with
+ | `Lifted1 g -> `Lifted1 (`Lifted2 g)
+ | `Lifted2 c -> `Lifted2 (G.CSet.of_list (G_backw.CSet.elements c))
+ | `Bot -> `Bot
+ | `Top -> `Top
+
+
+ (* actually relevant (transfer) functions *)
+ let sync_backw man man_forw =
+ match man.prev_node, Cfg.next man.prev_node with
+ | _, _ :: _ :: _ -> (* Join in CFG. *)
+ S_backw.sync man man_forw `Join
+ (* | FunctionEntry f, _ -> (* Function entry, also needs sync because partial contexts joined by solver, see 00-sanity/35-join-contexts. *)
+ S_backw.sync man man_forw (`JoinCall f) *)
+ | Function f, _ -> (* Function entry, also needs sync because partial contexts joined by solver, see 00-sanity/35-join-contexts. *)
+ S_backw.sync man man_forw (`JoinCall f)
+ | _, _ -> S_backw.sync man man_forw `Normal
+
+ let side_context_backw sideg f c =
+ if !AnalysisState.postsolving then
+ sideg (GVar.GV_backw.contexts f) (G_backw.create_contexts (G_backw.CSet.singleton c))
+
+
+ let create_basic_man_forw var edge prev_node pval getl getl_forw sidel demandl getg getg_forw sideg : (S_forw.D.t, S_forw.G.t, S_forw.C.t, S_forw.V.t) man =
+ (* let r = ref [] in *)
+ let node = fst var in
+ let context : (unit -> S_forw.C.t) = snd var |> Obj.obj in
+
+ let rec man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> S_forw.query man_forw q)
+ ; emit = (fun _ -> failwith "emit outside MCP")
+ ; node = fst var
+ ; prev_node = MyCFG.dummy_node (*I do not have *)
+ ; control_context = (fun () -> failwith "control context not implemented (yet) for forward manager.")
+ ; context = context
+ ; edge = edge
+ ; local = getl_forw (node, context())
+ ; global = (fun g -> G_forw.spec (getg_forw (GVar.GV_forw.spec g)))
+ ; spawn = (fun ?multiple _ _ _ -> failwith "spawn should not be called from forward manager")
+ ; split = (fun _ _ -> failwith "split? what does this do?")
+ ; sideg = (fun _ _ -> failwith "sideg should not be called from forward manager")
+ }
+ in
+ man_forw
+
+ let common_man_backw (var:node*Obj.t) edge prev_node pval getl getl_forw (sidel : node * S_forw.C.t -> S_backw.D.t -> unit) demandl getg getg_forw sideg : (S_backw.D.t, S_backw.G.t, S_backw.C.t, S_backw.V.t) man * S_backw.D.t list ref * (lval option * varinfo * exp list * S_backw.D.t * bool) list ref =
+ let r = ref [] in
+ let spawns = ref [] in
+
+ let man_forw = create_basic_man_forw var edge prev_node pval getl getl_forw sidel demandl getg getg_forw sideg in
+
+ (* Logs.debug "Created forward manager for node %a, now creating backward manager" Node.pretty (fst var); *)
+ (* now watch this ... *)
+ let rec man =
+ { ask = (fun (type a) (q: a Queries.t) -> S_backw.query man man_forw q)
+ ; emit = (fun _ -> failwith "emit outside MCP")
+ ; node = fst var
+ ; prev_node = prev_node
+ ; control_context = snd var |> Obj.obj
+ ; context = snd var |> Obj.obj
+ ; edge = edge
+ ; local = pval
+ ; global = (fun g -> G_backw.spec (getg (GVar.GV_backw.spec g)))
+ ; spawn = spawn
+ ; split = (fun (d:S_backw.D.t) es -> assert (List.is_empty es); r := d::!r)
+ ; sideg = (fun g d -> sideg (GVar.GV_backw.spec g) (G_backw.create_spec d))
+ }
+ and spawn ?(multiple=false) lval f args =
+ (* TODO: adjust man node/edge? *)
+ (* TODO: don't repeat for all paths that spawn same *)
+
+ (* TODO: This needs to be changed for backwards!! Context is created using S_backw.context*)
+ let ds = S_backw.threadenter ~multiple man man_forw lval f args in
+ List.iter (fun (d : S_backw.D.t) ->
+ spawns := (lval, f, args, d, multiple) :: !spawns;
+ match Cilfacade.find_varinfo_fundec f with
+ | fd ->
+ let c = S_forw.context man_forw fd (man_forw.local) in
+ (* sidel (FunctionEntry fd, c) d;
+ demandl (Function fd, c) *)
+ sidel (Function fd, c) d;
+ demandl (FunctionEntry fd, c)
+ | exception Not_found ->
+ (* unknown function *)
+ M.error ~category:Imprecise ~tags:[Category Unsound] "Created a thread from unknown function %s" f.vname;
+ (* actual implementation (e.g. invalidation) is done by threadenter *)
+ (* must still sync for side effects, e.g., old sync-based none privatization soundness in 02-base/51-spawn-special *)
+ let rec sync_man =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> (S_backw.query sync_man man_forw q));
+ local = d;
+ (* prev_node = Function dummyFunDec; *)
+ prev_node = FunctionEntry dummyFunDec;
+ }
+ in
+ (* TODO: more accurate man? *)
+ ignore (sync_backw sync_man man_forw)
+ ) ds
+ in
+ (* ... nice, right! *)
+ let pval = sync_backw man man_forw in
+ { man with local = pval }, r, spawns
+
+ let rec bigsqcup_backw = function
+ | [] -> S_backw.D.bot ()
+ | [x] -> x
+ | x::xs -> S_backw.D.join x (bigsqcup_backw xs)
+
+ let thread_spawns_backws man man_forw d spawns =
+ if List.is_empty spawns then
+ d
+ else
+ let rec man' =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query man' man_forw q)
+ ; local = d
+ }
+ in
+ (* TODO: don't forget path dependencies *)
+ let one_spawn (lval, f, args, fd, multiple) =
+ let rec fman =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query fman man_forw q)
+ ; local = fd
+ }
+ in
+ S_backw.threadspawn man' man_forw ~multiple lval f args fman
+ in
+ bigsqcup_backw (List.map one_spawn spawns)
+
+ let common_join_backw man man_forw d splits spawns =
+ thread_spawns_backws man man_forw (bigsqcup_backw (d :: splits)) spawns
+
+ let common_joins_backw man man_forw ds splits spawns = common_join_backw man man_forw (bigsqcup_backw ds) splits spawns
+
+ let tf_assign_backw var edge prev_node lv e getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.assign man man_forw lv e in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ let tf_vdecl_backw var edge prev_node v getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.vdecl man man_forw v in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ let normal_return_backw r fd man man_forw sideg =
+ let spawning_return = S_backw.return man man_forw r fd in
+ let nval = S_backw.sync { man with local = spawning_return } man_forw `Return in
+ nval
+
+ let toplevel_kernel_return_backw r fd man man_forw sideg =
+ let st = if fd.svar.vname = MyCFG.dummy_func.svar.vname then man.local else S_backw.return man man_forw r fd in
+ let spawning_return = S_backw.return {man with local = st} man_forw None MyCFG.dummy_func in
+ let nval = S_backw.sync { man with local = spawning_return } man_forw `Return in
+ nval
+
+ let tf_ret_backw var edge prev_node ret fd getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ if (CilType.Fundec.equal fd MyCFG.dummy_func ||
+ List.mem fd.svar.vname (get_string_list "mainfun")) &&
+ get_bool "kernel"
+ then toplevel_kernel_return_backw ret fd man man_forw sideg
+ else normal_return_backw ret fd man man_forw sideg
+ in
+ common_join_backw man man_forw d !r !spawns
+
+ let tf_entry_backw var edge prev_node fd getl getl_forw sidel demandl getg getg_forw sideg d =
+ (* Side effect function context here instead of at sidel to FunctionEntry,
+ because otherwise context for main functions (entrystates) will be missing or pruned during postsolving. *)
+ let c: unit -> S_forw.C.t = snd var |> Obj.obj in
+ side_context_backw sideg fd (c ());
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.body man man_forw fd in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ let tf_test_backw var edge prev_node e tv getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.branch man man_forw e tv in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ (*TODO: THIS HAS TO BE BACKWARDS*) (*forward context not implemented yet*)
+ let tf_normal_call_backw man man_forw lv e (f:fundec) args getl (getl_forw : node * S_forw.C.t -> S_forw.D.t) sidel demandl getg getg_forw sideg =
+ let combine (cd, fc, fd) =
+ if M.tracing then M.traceli "combine" "local: %a" S_backw.D.pretty cd;
+ if M.tracing then M.trace "combine" "function: %a" S_backw.D.pretty fd;
+
+ (* Logs.debug "combine: local: %a" S_backw.D.pretty cd;
+ Logs.debug "combine: function: %a" S_backw.D.pretty fd; *)
+
+ let rec cd_man =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query cd_man man_forw q);
+ local = cd;
+ }
+ in
+ let fd_man =
+ (* Inner scope to prevent unsynced fd_man from being used. *)
+ (* Extra sync in case function has multiple returns.
+ Each `Return sync is done before joining, so joined value may be unsound.
+ Since sync is normally done before tf (in common_man), simulate it here for fd. *)
+ (* TODO: don't do this extra sync here *)
+ let rec sync_man =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query sync_man man_forw q);
+ local = fd;
+ (*prev_node = Function f*)
+ prev_node = FunctionEntry f;
+ }
+ in
+ (* TODO: more accurate man? *)
+ let synced = sync_backw sync_man man_forw in
+ let rec fd_man =
+ { sync_man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query fd_man man_forw q);
+ local = synced;
+ }
+ in
+ fd_man
+ in
+ let r = List.fold_left (fun acc fd1 ->
+ let rec fd1_man =
+ { fd_man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query fd1_man man_forw q);
+ local = fd1;
+ }
+ in
+ let combine_enved = S_backw.combine_env cd_man man_forw lv e f args fc fd1_man.local (Analyses.ask_of_man fd1_man) in
+ let rec combine_assign_man =
+ { cd_man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query combine_assign_man man_forw q);
+ local = combine_enved;
+ }
+ in
+ S_backw.D.join acc (S_backw.combine_assign combine_assign_man man_forw lv e f args fc fd1_man.local (Analyses.ask_of_man fd1_man))
+ ) (S_backw.D.bot ()) (S_backw.paths_as_set fd_man man_forw)
+ in
+ if M.tracing then M.traceu "combine" "combined local: %a" S_backw.D.pretty r;
+ (* Logs.debug "combined local: %a" S_backw.D.pretty r; *)
+ r
+ in
+ let paths = S_backw.enter man man_forw lv f args in
+
+ (* getl_forw should query the corresopoding unknown from the forward analysis *)
+ (* context = S_forw.context (S_forw.enter (getl_forw [this_node_, this_context])) *)
+
+ let man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> failwith "manager for calculating context does not support queries")
+ ; emit = (fun _ -> failwith "emit outside MCP")
+ ; node = man.node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = man.control_context
+ ; context = man.context
+ ; edge = man.edge
+ ; local = (getl_forw (man.node, man.context ()))
+ ; global = (fun g -> G_forw.spec (getg_forw (GVar.GV_forw.spec g)))
+ ; spawn = (fun ?multiple _ _ _ -> failwith "manager for calculating context does not support spawn")
+ ; split = (fun _ _ -> failwith "manager for calculating context does not support split")
+ ; sideg = (fun _ _ -> failwith "manager for calculating context does not support sideg")
+ } in
+
+ let paths_forw =
+ Logs.debug "forward manager info at call to %a" Node.pretty man_forw.node;
+ S_forw.enter man_forw lv f args in
+
+ let paths = List.combine paths paths_forw in
+
+ (* filter paths were the forward analysis found out they are unreachable*)
+ let paths = List.filter (fun ((c,v),(_,b)) -> not (S_forw.D.is_bot b)) paths in
+
+ (* this list now uses forward contexts*)
+ let paths = List.map (fun ((c,v),(_,b)) -> (c, S_forw.context man_forw f b, v)) paths in
+
+ (* The two lines below is what I should use. *)
+ (* List.iter (fun (c,fc,v) -> if not (S_backw.D.is_bot v) then sidel (Function f, fc) v) paths; *)
+ (* let paths = List.map (fun (c,fc,v) -> (c, fc, if S_backw.D.is_bot v then v else getl (FunctionEntry f, fc))) paths in *)
+
+ (* A problem with my liveness analysis is that D.empty = D.bot, but I still need to evaluate a function since variables might become live inside. This is not optimal and the liveness analysis should be changed.*)
+ List.iter (fun (c,fc,v) -> sidel (Function f, fc) v) paths;
+ let paths = List.map (fun (c,fc,v) -> (c, fc, getl (FunctionEntry f, fc))) paths in
+
+
+ (* Don't filter bot paths, otherwise LongjmpLifter is not called. *)
+ (* let paths = List.filter (fun (c,fc,v) -> not (D.is_bot v)) paths in *)
+ let paths = List.map (Tuple3.map2 Option.some) paths in
+ if M.tracing then M.traceli "combine" "combining";
+ (* Logs.debug "combining"; *)
+ let paths = List.map combine paths in
+ let r = List.fold_left S_backw.D.join (S_backw.D.bot ()) paths in
+ if M.tracing then M.traceu "combine" "combined: %a" S_backw.D.pretty r;
+ (* Logs.debug "combined: %a" S_backw.D.pretty r; *)
+ r
+
+ let rec tf_proc_backw var edge prev_node lv e args getl (getl_forw: node * S_forw.C.t -> S_forw.D.t) sidel demandl getg getg_forw sideg d =
+ let tf_special_call man man_forw f =
+ let once once_control init_routine =
+ (* Executes leave event for new local state d if it is not bottom *)
+ let leave_once d =
+ if not (S_backw.D.is_bot d) then
+ let rec man' =
+ { man with
+ ask = (fun (type a) (q: a Queries.t) -> S_backw.query man' man_forw q);
+ local = d;
+ }
+ in
+ S_backw.event man' man_forw (Events.LeaveOnce { once_control }) man'
+ else
+ S_backw.D.bot ()
+ in
+ let first_call =
+ let d' = S_backw.event man man_forw (Events.EnterOnce { once_control; ran = false }) man in
+ tf_proc_backw var edge prev_node None init_routine [] getl getl_forw sidel demandl getg getg_forw sideg d'
+ in
+ let later_call = S_backw.event man man_forw (Events.EnterOnce { once_control; ran = true }) man in
+ S_backw.D.join (leave_once first_call) (leave_once later_call)
+ in
+ let is_once = LibraryFunctions.find ~nowarn:true f in
+ (* If the prototpye for a library function is wrong, this will throw an exception. Such exceptions are usually unrelated to pthread_once, it is just that the call to `is_once.special` raises here *)
+ match is_once.special args with
+ | Once { once_control; init_routine } -> once once_control init_routine
+ | _ -> S_backw.special man man_forw lv f args
+ in
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let functions =
+ match e with
+ | Lval (Var v, NoOffset) ->
+ (* Handle statically known function call directly.
+ Allows deactivating base. *)
+ [v]
+ | _ ->
+ (*constructing fake forwards manager s.t. the inforamtion for the pointer information can be retireved*)
+ (* let r = ref [] in
+ let rec man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> S_forw.query man_forw q)
+ ; emit = (fun _ -> failwith "emit outside MCP")
+ ; node = man.node
+ ; prev_node = man.prev_node (* this is problematic, as this is backwards *)
+ ; control_context = man.control_context
+ ; context = man.context
+ ; edge = man.edge
+ ; local = (getl_forw (man.node, man.context ())) (* accessing forward inforkation*)
+ ; global = (fun _ -> failwith "whoops, query for resolving function pointer depends on globals")
+ ; spawn = (fun ?multiple _ _ _ -> failwith "manager for resolving function pointer does not support spawn")
+ ; split = (fun (d:S_forw.D.t) es -> assert (List.is_empty es); r := d::!r) (*what is this?*)
+ ; sideg = (fun _ _ -> failwith "manager for resolving function pointer does not support sideg")
+ } in *)
+ let () = Logs.debug "manager info at call to function pointer %a" Node.pretty man_forw.node in
+ (* Depends on base for query. *)
+ let ad = man_forw.ask (Queries.EvalFunvar e) in
+ let res = Queries.AD.to_var_may ad in (* TODO: don't convert, handle UnknownPtr below *)
+ (*PROBLEM: Pointer. Brauche Ergebnisse der anderen Analysen*)
+ (Logs.debug "(!) resolved function pointer to %d functions" (List.length res);
+ (match res with
+ | x::xs ->
+ List.iter (fun vi -> Logs.debug " possible function: %s" vi.vname) res;
+ | _ -> ();
+ ));
+ res
+ in
+ let one_function f =
+ match Cil.unrollType f.vtype with
+ | TFun (_, params, var_arg, _) ->
+ let arg_length = List.length args in
+ let p_length = Option.map_default List.length 0 params in
+ (* Check whether number of arguments fits. *)
+ (* If params is None, the function or its parameters are not declared, so we still analyze the unknown function call. *)
+ if Option.is_none params || p_length = arg_length || (var_arg && arg_length >= p_length) then
+ let d =
+ (match Cilfacade.find_varinfo_fundec f with
+ | fd when LibraryFunctions.use_special f.vname ->
+ M.info ~category:Analyzer "Using special for defined function %s" f.vname;
+ tf_special_call man man_forw f
+ | fd ->
+ tf_normal_call_backw man man_forw lv e fd args getl getl_forw sidel demandl getg getg_forw sideg
+ | exception Not_found ->
+ tf_special_call man man_forw f)
+ in
+ Some d
+ else begin
+ let geq = if var_arg then ">=" else "" in
+ M.warn ~category:Unsound ~tags:[Category Call; CWE 685] "Potential call to function %a with wrong number of arguments (expected: %s%d, actual: %d). This call will be ignored." CilType.Varinfo.pretty f geq p_length arg_length;
+ None
+ end
+ | _ ->
+ M.warn ~category:Call "Something that is not a function (%a) is called." CilType.Varinfo.pretty f;
+ None
+ in
+ let funs = List.filter_map one_function functions in
+ if [] = funs && not (S_backw.D.is_bot man.local) then begin
+ M.msg_final Warning ~category:Unsound ~tags:[Category Call] "No suitable function to call";
+ M.warn ~category:Unsound ~tags:[Category Call] "No suitable function to be called at call site. Continuing with state before call.";
+ d (* because LevelSliceLifter *)
+ end else
+ common_joins_backw man man_forw funs !r !spawns
+
+ let tf_asm_backw var edge prev_node getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.asm man man_forw in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ let tf_skip_backw var edge prev_node getl getl_forw sidel demandl getg getg_forw sideg d =
+ let man, r, spawns = common_man_backw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let man_forw = create_basic_man_forw var edge prev_node d getl getl_forw sidel demandl getg getg_forw sideg in
+ let d = S_backw.skip man man_forw in (* Force transfer function to be evaluated before dereferencing in common_join argument. *)
+ common_join_backw man man_forw d !r !spawns
+
+ let tf_backw var getl getl_forw sidel demandl getg getg_forw sideg prev_node edge d =
+ begin match edge with
+ | Assign (lv,rv) -> tf_assign_backw var edge prev_node lv rv
+ | VDecl (v) -> tf_vdecl_backw var edge prev_node v
+ | Proc (r,f,ars) -> tf_proc_backw var edge prev_node r f ars
+ | Entry f -> tf_entry_backw var edge prev_node f
+ | Ret (r,fd) -> tf_ret_backw var edge prev_node r fd
+ | Test (p,b) -> tf_test_backw var edge prev_node p b
+ | ASM (_, _, _) -> tf_asm_backw var edge prev_node (* TODO: use ASM fields for something? *)
+ | Skip -> tf_skip_backw var edge prev_node
+ end getl getl_forw sidel demandl getg getg_forw sideg d
+
+ (* TODO: Don't call it prev_node when it is actually the next node. *)
+ let tf_backw var getl getl_forw sidel demandl getg getg_forw sideg prev_node (_,edge) d (f,t) =
+ (* let old_loc = !Goblint_tracing.current_loc in
+ let old_loc2 = !Goblint_tracing.next_loc in
+ Goblint_tracing.current_loc := f;
+ Goblint_tracing.next_loc := t;
+ Goblint_backtrace.protect ~mark:(fun () -> TfLocation f) ~finally:(fun () ->
+ Goblint_tracing.current_loc := old_loc;
+ Goblint_tracing.next_loc := old_loc2
+ ) (fun () ->
+ let d = tf_backw var getl sidel demandl getg sideg prev_node edge d in
+ d
+ ) *)
+ tf_backw var getl getl_forw sidel demandl getg getg_forw sideg prev_node edge d
+
+ let tf_backw (v,c) (edges, u) getl getl_forw sidel demandl getg getg_forw sideg =
+ let pval = getl (u,c) in
+ let _, locs = List.fold_right (fun (f,e) (t,xs) -> f, (f,t)::xs) edges (Node.location v,[]) in
+ List.fold_left2 (|>) pval (List.map (tf_backw (v,Obj.repr (fun () -> c)) getl getl_forw sidel demandl getg getg_forw sideg u) edges) locs
+
+ let tf_backw (v,c) (e,u) getl getl_forw sidel demandl getg getg_forw sideg =
+ let old_node = !current_node in
+ let old_fd = Option.map Node.find_fundec old_node |? Cil.dummyFunDec in
+ let new_fd = Node.find_fundec v in
+ if not (CilType.Fundec.equal old_fd new_fd) then
+ Timing.Program.enter new_fd.svar.vname;
+ let old_context = !M.current_context in
+ current_node := Some u;
+ M.current_context := Some (Obj.magic c); (* magic is fine because Spec is top-level Control Spec *)
+ Fun.protect ~finally:(fun () ->
+ current_node := old_node;
+ M.current_context := old_context;
+ if not (CilType.Fundec.equal old_fd new_fd) then
+ Timing.Program.exit new_fd.svar.vname
+ ) (fun () ->
+ let d = tf_backw (v,c) (e,u) getl getl_forw sidel demandl getg getg_forw sideg in
+ d
+ )
+
+ let system_backw (v,c) =
+
+ match v with
+ | Function _ -> None
+ | _ ->
+ let tf_backw getl sidel demandl getg sideg =
+ let getl_backw d = getl (`L_backw d) |> to_backw_d in
+ let getl_forw d = getl (`L_forw d) |> to_forw_d in
+ let getg_backw v = getg (`Backw v) |> to_backw_g in
+ let getg_forw v = getg (`Forw v) |> to_forw_g in
+ let tf' eu = tf_backw (v,c) eu getl_backw getl_forw sidel demandl getg_backw getg_forw sideg in
+ let xs = List.map tf' (Cfg.next v) in
+ List.fold_left S_backw.D.join (S_backw.D.bot ()) xs
+ in
+ Some tf_backw
+
+ let system var =
+
+ (* let log () =
+ match var with
+ | `L_forw (v, _) -> Logs.debug "(*) Creating tf for forward variable %a" Node.pretty v
+ | `L_backw (v, _) -> Logs.debug "(*) Creating tf for backward variable %a" Node.pretty v
+ in
+ log(); *)
+
+ match var with
+ | `L_forw v ->
+ Forward.system v
+ |> Option.map (fun tf getl sidel demandl getg sideg ->
+ let getl' v = getl (`L_forw v) |> to_forw_d in
+ let sidel' v d = sidel (`L_forw v) (`Lifted1 d) in
+ let demandl' v = demandl (`L_forw v) in
+ let getg' v = getg (`Forw v) |> to_forw_g in
+ let sideg' v d = sideg (`Forw v) (of_forw_g d) in
+ tf getl' sidel' demandl' getg' sideg' |> (fun d -> `Lifted1 d)
+ )
+ | `L_backw v ->
+ system_backw v
+ |> Option.map (fun tf getl sidel demandl getg sideg ->
+ let sidel' v d = sidel (`L_backw v) (`Lifted2 d) in
+ let demandl' v = demandl (`L_backw v) in
+ let sideg' v d = sideg (`Backw v) (of_backw_g d) in
+ tf getl sidel' demandl' getg sideg' |> (fun d -> `Lifted2 d)
+ )
+
+ let iter_vars getl getg vq fl fg =
+ failwith "iter_vars not implemented for bidirectional constraint system."
+
+ let sys_change getl getg =
+ failwith "sys_change not implemented for bidirectional constraint system."
+
+ let postmortem_backw v =
+ failwith "postmortem not implemented for backward analysis"
+ (* match v with
+ | Function fd, c -> [(FunctionEntry fd, c)]
+ | _ -> [] *)
+
+ let postmortem = function
+ | `L_forw v -> List.map (fun v -> `L_forw v) (Forward.postmortem v)
+ | `L_backw v -> List.map (fun v -> `L_backw (v)) (postmortem_backw v)
+end
\ No newline at end of file
diff --git a/src/framework/control.ml b/src/framework/control.ml
index 06b07e6f41..d9fd6a79bb 100644
--- a/src/framework/control.ml
+++ b/src/framework/control.ml
@@ -6,12 +6,14 @@ open Batteries
open GoblintCil
open MyCFG
open Analyses
+open BackwAnalyses
open Goblint_constraint.ConstrSys
open Goblint_constraint.Translators
open Goblint_constraint.SolverTypes
open GobConfig
open Constraints
open SpecLifters
+open BidirConstrains
module type S2S = Spec2Spec
@@ -153,7 +155,7 @@ struct
let open Cilfacade in
let warn_for_upjumps fundec gotos =
if FunSet.mem live_funs fundec then (
- (* set nortermiantion flag *)
+ (* set nontermination flag *)
AnalysisState.svcomp_may_not_terminate := true;
(* iterate through locations to produce warnings *)
LocSet.iter (fun l _ ->
@@ -248,7 +250,7 @@ struct
AnalysisState.should_warn := false; (* reset for server mode *)
- (* exctract global xml from result *)
+ (* extract global xml from result *)
let make_global_fast_xml f g =
let open Printf in
let print_globals k v =
@@ -843,16 +845,833 @@ struct
Timing.wrap "result output" (ResultOutput.output (lazy local_xml) liveness gh make_global_fast_xml) (module FileCfg)
end
-(* This function was originally a part of the [AnalyzeCFG] module, but
- now that [AnalyzeCFG] takes [Spec] as a functor parameter,
- [analyze_loop] cannot reside in it anymore since each invocation of
- [get_spec] in the loop might/should return a different module, and we
- cannot swap the functor parameter from inside [AnalyzeCFG]. *)
+(** Given a [Cfg], a [Spec_forw], [Spec_back], and an unused [Inc], computes the solution*)
+module AnalyzeCFG_bidir (Cfg:CfgBidirSkip) (Spec_forw:Spec) (BackwSpecSpec : BackwAnalyses.BackwSpecSpec) (Inc:Increment) =
+struct
+ module Spec_backw = BackwSpecSpec (Spec_forw)
+ (* The Equation system *)
+ module EQSys = BidirConstrains.BidirFromSpec (Spec_forw) (Spec_backw) (Cfg) (Inc)
+
+ (* Hashtbl for locals *)
+ module LHT = BatHashtbl.Make (EQSys.LVar)
+ (* Hashtbl for globals *)
+ module GHT = BatHashtbl.Make (EQSys.GVar)
+
+ (* The solver *)
+ module PostSolverArg =
+ struct
+ let should_prune = true
+ let should_verify = get_bool "verify"
+ let should_warn = get_string "warn_at" <> "never"
+ let should_save_run =
+ (* copied from solve_and_postprocess *)
+ let gobview = get_bool "gobview" in
+ let save_run = let o = get_string "save_run" in if o = "" then (if gobview then "run" else "") else o in
+ save_run <> ""
+ end
+ module Slvr = (GlobSolverFromEqSolver (Goblint_solver.Selector.Make (PostSolverArg))) (EQSys) (LHT) (GHT)
+
+ (* Forward result module *)
+ (* Triple of the function, context, and the local value. It uses Spec and therefore has the wrong types.*)
+ module type ResultBundle = sig
+ module Spec : Spec
+ module RT : module type of AnalysisResult.ResultType2 (Spec)
+ module LT : module type of SetDomain.HeadlessSet (RT)
+ module Result : module type of AnalysisResult.Result (LT) (struct let result_name = "" end)
+ module ResultOutput : module type of AnalysisResultOutput.Make (Result)
+ end
+
+ module ResBundle_forw : ResultBundle with module Spec = Spec_forw =
+ struct
+ module Spec = Spec_forw
+ module RT = AnalysisResult.ResultType2 (Spec_forw)
+ module LT = SetDomain.HeadlessSet (RT)
+ module Result = AnalysisResult.Result (LT) (struct let result_name = "analysis_forw" end)
+ module ResultOutput = AnalysisResultOutput.Make (Result)
+ end
+
+ (** this function converts the LHT to two Results of forward type*)
+ let solver2source_result h =
+ let res_forw = ResBundle_forw.Result.create 113 in
+ (* let res_backw = ResBundle_backw.Result.create 113 in *)
+
+ (* Adding the state at each system variable to the final result *)
+ let add_local_var_forw (n,es) state =
+ (* Not using Node.location here to have updated locations in incremental analysis.
+ See: https://github.com/goblint/analyzer/issues/290#issuecomment-881258091. *)
+ let state = match state with
+ | `Lifted1 s -> s
+ | `Bot -> Spec_forw.D.bot ()
+ | `Top -> Spec_forw.D.top ()
+ | `Lifted2 _ -> failwith "Unexpected backward state in forward result"
+ in
+
+ let loc = UpdateCil.getLoc n in
+ if loc <> locUnknown then try
+ let fundec = Node.find_fundec n in
+ if ResBundle_forw.Result.mem res_forw n then
+ (* If this source location has been added before, we look it up
+ * and add another node to it information to it. *)
+ let prev = ResBundle_forw.Result.find res_forw n in
+ ResBundle_forw.Result.replace res_forw n (ResBundle_forw.LT.add (es,state,fundec) prev)
+ else
+ ResBundle_forw.Result.add res_forw n (ResBundle_forw.LT.singleton (es,state,fundec))
+ (* If the function is not defined, and yet has been included to the
+ * analysis result, we generate a warning. *)
+ with Not_found ->
+ Messages.debug ~category:Analyzer ~loc:(CilLocation loc) "Calculated state for undefined function: unexpected node %a" Node.pretty_trace n
+ in
+
+ LHT.iter (fun key ->
+ match key with
+ | `L_forw (n,es) -> add_local_var_forw (n,es)
+ | `L_backw (n,es) -> (fun _ -> ()) (* add_local_var_backw (n, es))*) ) h;
+
+ res_forw(*, res_backw*)
+
+
+ (** [analyze file startfuns exitfuns otherfuns] is the main function to preform the selected analyses.*)
+ let analyze (file: file) (startfuns, exitfuns, otherfuns: Analyses.fundecs) =
+ let module FileCfg: FileCfg =
+ struct
+ let file = file
+ module Cfg = Cfg
+ end
+ in
+
+ let module GV_forw = GVarF (Spec_forw.V) in
+ let module GV_backw = GVarF (Spec_backw.V) in
+
+ let module G_forw = GVarG (Spec_forw.G) (Spec_forw.C) in
+ let module G_backw = GVarG (Spec_backw.G) (Spec_backw.C) in
+
+
+ let log_analysis_setup () =
+ let log_fun_list name funs =
+ let fun_names = List.map (fun f -> f.svar.vname) funs in
+ Logs.debug "%s functions: %s" name (String.concat ", " fun_names)
+ in
+ Logs.debug "================= Analysis Setup ================";
+ log_fun_list "Start" startfuns;
+ log_fun_list "Exit" exitfuns;
+ log_fun_list "Other" otherfuns;
+ Logs.debug "=================================================";
+ in
+ log_analysis_setup ();
+
+ AnalysisState.should_warn := false;
+
+ (* initialize hastable for globals*)
+ let gh = GHT.create 13 in
+ let getg v = GHT.find_default gh v (EQSys.G.bot ()) in
+ let sideg v d = GHT.replace gh v (EQSys.G.join (getg v) d)
+ in
+
+ (** this function calculates and returns [startvars'_forw] and [entrystates_forw] *)
+ let do_forward_inits () : (node * Spec_forw.C.t) list * ((node * Spec_forw.C.t) * Spec_forw.D.t) list =
+
+ (* wrapping functions accessing and modifying global variables *)
+ let sideg_forw v d = sideg (`Forw (v)) ((`Lifted1 d)) in
+
+ (* the intit globals should not depend on each other*)
+ let getg_forw v = G_forw.bot () in
+
+ let do_extern_inits_forw man (file: file) : Spec_forw.D.t =
+ let module VS = Set.Make (Basetype.Variables) in
+ let add_glob s = function
+ | GVar (v,_,_) -> VS.add v s
+ | _ -> s
+ in
+ let vars = foldGlobals file add_glob VS.empty in
+ let set_bad v st =
+ Spec_forw.assign {man with local = st} (var v) MyCFG.unknown_exp
+ in
+ let is_std = function
+ | {vname = ("__tzname" | "__daylight" | "__timezone"); _} (* unix time.h *)
+ | {vname = ("tzname" | "daylight" | "timezone"); _} (* unix time.h *)
+ | {vname = "getdate_err"; _} (* unix time.h, but somehow always in MacOS even without include *)
+ | {vname = ("stdin" | "stdout" | "stderr"); _} (* standard stdio.h *)
+ | {vname = ("optarg" | "optind" | "opterr" | "optopt" ); _} (* unix unistd.h *)
+ | {vname = ("__environ"); _} -> (* Linux Standard Base Core Specification *)
+ true
+ | _ -> false
+ in
+ let add_externs s = function
+ | GVarDecl (v,_) when not (VS.mem v vars || isFunctionType v.vtype) && not (get_bool "exp.hide-std-globals" && is_std v) -> set_bad v s
+ | _ -> s
+ in
+ Logs.debug "startstate of Spec_forw: %a" Spec_forw.D.pretty (Spec_forw.startstate MyCFG.dummy_func.svar);
+ foldGlobals file add_externs (Spec_forw.startstate MyCFG.dummy_func.svar)
+ in
+
+ (** this function uses cil's global-inits function to get a starting state *)
+ let do_global_inits_forw (file: file) : Spec_forw.D.t * fundec list =
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in global initializer context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "Global initializers have no context.")
+ ; context = (fun () -> man_failwith "Global initializers have no context.")
+ ; edge = MyCFG.Skip
+ ; local = Spec_forw.D.top ()
+ ; global = (fun g -> G_forw.spec (getg_forw (GV_forw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Global initializers should never spawn threads. What is going on?")
+ ; split = (fun _ -> failwith "Global initializers trying to split paths.")
+ ; sideg = (fun g d -> sideg_forw (GV_forw.spec g) (G_forw.create_spec d))
+ }
+ in
+
+ let edges = CfgTools.getGlobalInits file in
+ Logs.debug "Executing %d assigns." (List.length edges);
+ let funs = ref [] in
+
+ let transfer_func (st : Spec_forw.D.t) (loc, edge) : Spec_forw.D.t =
+ match edge with
+ | MyCFG.Entry func -> Spec_forw.body {man with local = st} func
+ | MyCFG.Assign (lval,exp) ->
+ begin match lval, exp with
+ | (Var v,o), (AddrOf (Var f,NoOffset))
+ when v.vstorage <> Static && isFunctionType f.vtype ->
+ (try funs := Cilfacade.find_varinfo_fundec f :: !funs with Not_found -> ())
+ | _ -> ()
+ end;
+ let res = Spec_forw.assign {man with local = st} lval exp in
+ (* Needed for privatizations (e.g. None) that do not side immediately *)
+ let res' = Spec_forw.sync {man with local = res} `Normal in
+ res'
+ | _ -> failwith "Unsupported global initializer edge"
+ in
+
+ let with_externs = do_extern_inits_forw man file in
+ Logs.debug "witch_externs: %a" Spec_forw.D.pretty with_externs;
+ let result : Spec_forw.D.t = List.fold_left transfer_func with_externs edges in
+ result, !funs
+ in
+
+ let startstate, _ = do_global_inits_forw file in
+
+ (** calculate startvars *)
+ let calculate_startvars_forw () =
+
+ let enter_with st fd =
+ let st = st fd.svar in
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_with has no control_context.")
+ ; context = Spec_forw.startcontext
+ ; edge = MyCFG.Skip
+ ; local = st
+ ; global = (fun g -> G_forw.spec (getg_forw (GV_forw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_forw (GV_forw.spec g) (G_forw.create_spec (d)))
+ }
+ in
+ let args = List.map (fun x -> MyCFG.unknown_exp) fd.sformals in
+ let ents = Spec_forw.enter man None fd args in
+ List.map (fun (_,s) -> fd, s) ents
+ in
+
+ (try MyCFG.dummy_func.svar.vdecl <- (List.hd otherfuns).svar.vdecl with Failure _ -> ());
+
+ let startvars =
+ if startfuns = []
+ then [[MyCFG.dummy_func, startstate]]
+ else
+ let morph f = Spec_forw.morphstate f startstate in
+ List.map (enter_with morph) startfuns
+ in
+
+ let exitvars = List.map (enter_with Spec_forw.exitstate) exitfuns in
+ let otherstate st v =
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in otherstate context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_func has no context.")
+ ; context = (fun () -> man_failwith "enter_func has no context.")
+ ; edge = MyCFG.Skip
+ ; local = st
+ ; global = (fun g -> G_forw.spec (getg_forw (GV_forw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_forw (GV_forw.spec g) (G_forw.create_spec (d)))
+ }
+ in
+ (* TODO: don't hd *)
+ List.hd (Spec_forw.threadenter man ~multiple:false None v [])
+ (* TODO: do threadspawn to mainfuns? *)
+ in
+ let prestartstate = Spec_forw.startstate MyCFG.dummy_func.svar in (* like in do_extern_inits *)
+ let othervars = List.map (enter_with (otherstate prestartstate)) otherfuns in
+ let startvars = List.concat (startvars @ exitvars @ othervars) in
+ if startvars = [] then
+ failwith "BUG: Empty set of start variables; may happen if enter_func of any analysis returns an empty list.";
+
+ AnalysisState.global_initialization := false;
+
+ let man e =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_with has no control_context.")
+ ; context = Spec_forw.startcontext
+ ; edge = MyCFG.Skip
+ ; local = e
+ ; global = (fun g -> G_forw.spec (getg_forw (GV_forw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_forw (GV_forw.spec g) (G_forw.create_spec d))
+ }
+ in
+ let startvars' = List.map (fun (n,e) -> (MyCFG.Function n, Spec_forw.context (man e) n e)) startvars in
+ let entrystates = List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec_forw.context (man e) n e), e) startvars in
+
+ startvars', entrystates
+ in
+
+ calculate_startvars_forw ()
+ in
+
+ (** this function calculates and returns [startvars'_backw] and [entrystates_backw] *)
+ let do_backward_inits () : (node * Spec_backw.C.t) list * ((node * Spec_forw.C.t) * Spec_backw.D.t) list =
+
+ let sideg_backw v d = sideg (`Backw v) (EQSys.G.create_spec (`Lifted2 d)) in
+
+ (* the intit globals should not depend on each other*)
+ let getg_backw v = G_backw.bot () in
+
+ let do_extern_inits_backw man man_forw (file: file) : Spec_backw.D.t =
+ let module VS = Set.Make (Basetype.Variables) in
+ let add_glob s = function
+ | GVar (v,_,_) -> VS.add v s
+ | _ -> s
+ in
+ let vars = foldGlobals file add_glob VS.empty in
+ let set_bad v st =
+ Spec_backw.assign {man with local = st} man_forw (var v) MyCFG.unknown_exp
+ in
+ let is_std = function
+ | {vname = ("__tzname" | "__daylight" | "__timezone"); _} (* unix time.h *)
+ | {vname = ("tzname" | "daylight" | "timezone"); _} (* unix time.h *)
+ | {vname = "getdate_err"; _} (* unix time.h, but somehow always in MacOS even without include *)
+ | {vname = ("stdin" | "stdout" | "stderr"); _} (* standard stdio.h *)
+ | {vname = ("optarg" | "optind" | "opterr" | "optopt" ); _} (* unix unistd.h *)
+ | {vname = ("__environ"); _} -> (* Linux Standard Base Core Specification *)
+ true
+ | _ -> false
+ in
+ let add_externs s = function
+ | GVarDecl (v,_) when not (VS.mem v vars || isFunctionType v.vtype) && not (get_bool "exp.hide-std-globals" && is_std v) -> set_bad v s
+ | _ -> s
+ in
+ foldGlobals file add_externs (Spec_backw.startstate MyCFG.dummy_func.svar)
+ in
+
+ (** This function analyses cil's global-inits function to get a starting state *)
+ let do_global_inits_backw (file: file) : Spec_backw.D.t * fundec list =
+
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in global initializer context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "Global initializers have no context.")
+ ; context = (fun () -> man_failwith "Global initializers have no context.")
+ ; edge = MyCFG.Skip
+ ; local = Spec_backw.D.top ()
+ ; global = (fun g -> G_backw.spec (getg_backw (GV_backw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Global initializers should never spawn threads. What is going on?")
+ ; split = (fun _ -> failwith "Global initializers trying to split paths.")
+ ; sideg = (fun g d -> sideg_backw (GV_backw.spec g) d)
+ }
+ in
+
+ let man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in global initializer context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "Global initializers have no context.")
+ ; context = (fun () -> man_failwith "Global initializers have no context.")
+ ; edge = MyCFG.Skip
+ ; local = Spec_forw.D.top () (*Should probably use local from already initialized forward variable.*)
+ ; global = (fun _ -> Spec_forw.G.bot ())
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Global initializers should never spawn threads. What is going on?")
+ ; split = (fun _ -> failwith "Global initializers trying to split paths.")
+ ; sideg = (fun _ _ -> failwith "forw_man in the backwards initialization should not be used to sideeffect globals.")
+ }
+ in
+
+ let edges = CfgTools.getGlobalInits file in
+ Logs.debug "Executing %d assigns." (List.length edges);
+ let funs = ref [] in
+
+ let transfer_func (st : Spec_backw.D.t) (loc, edge) : Spec_backw.D.t =
+ match edge with
+ | MyCFG.Entry func -> Spec_backw.body {man with local = st} man_forw func
+ | MyCFG.Assign (lval,exp) ->
+ begin match lval, exp with
+ | (Var v,o), (AddrOf (Var f,NoOffset))
+ when v.vstorage <> Static && isFunctionType f.vtype ->
+ (try funs := Cilfacade.find_varinfo_fundec f :: !funs with Not_found -> ())
+ | _ -> ()
+ end;
+ let res = Spec_backw.assign {man with local = st} man_forw lval exp in
+ (* Needed for privatizations (e.g. None) that do not side immediately *)
+ let res' = Spec_backw.sync {man with local = res} man_forw `Normal in
+ res'
+ | _ -> failwith "Unsupported global initializer edge"
+ in
+
+ let with_externs = do_extern_inits_backw man man_forw file in
+ let result : Spec_backw.D.t = List.fold_left transfer_func with_externs edges in
+ result, !funs
+ in
+
+ let startstate, _ = do_global_inits_backw file in
+
+ (** calculate startvars *)
+ let calculate_startvars_backw () =
+
+ let enter_with st fd =
+ let st = st fd.svar in
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_with has no control_context.")
+ ; context = Spec_forw.startcontext
+ ; edge = MyCFG.Skip
+ ; local = st
+ ; global = (fun g -> G_backw.spec (getg_backw (GV_backw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_backw (GV_backw.spec g) d)
+ }
+ in
+ let man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in global initializer context.")
+ ; node = man.node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "Global initializers have no context.")
+ ; context = man.context
+ ; edge = MyCFG.Skip
+ ; local = Spec_forw.D.top () (*Should probably use local from already initialized forward variable.*)
+ ; global = (fun _ -> Spec_forw.G.bot ())
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Global initializers should never spawn threads. What is going on?")
+ ; split = (fun _ -> failwith "Global initializers trying to split paths.")
+ ; sideg = (fun _ _ -> failwith "forw_man in the backwards initialization should not be used to sideeffect globals.")
+ }
+ in
+
+ let args = List.map (fun x -> MyCFG.unknown_exp) fd.sformals in
+ let ents = Spec_backw.enter man man_forw None fd args in
+ List.map (fun (_,s) -> fd, s) ents
+ in
+
+ (try MyCFG.dummy_func.svar.vdecl <- (List.hd otherfuns).svar.vdecl with Failure _ -> ());
+
+ let startvars =
+ if startfuns = []
+ then [[MyCFG.dummy_func, startstate]]
+ else
+ let morph f = Spec_backw.morphstate f startstate in
+ List.map (enter_with morph) startfuns
+ in
+
+ let exitvars = List.map (enter_with Spec_backw.exitstate) exitfuns in
+ let otherstate st v =
+ let man =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in otherstate context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_func has no context.")
+ ; context = (fun () -> man_failwith "enter_func has no context.")
+ ; edge = MyCFG.Skip
+ ; local = st
+ ; global = (fun g -> G_backw.spec (getg_backw (GV_backw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_backw (GV_backw.spec g) d)
+ }
+ in
+
+ let man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in otherstate context.")
+ ; node = man.node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_func has no context.")
+ ; context = (fun () -> man_failwith "enter_func has no context.")
+ ; edge = MyCFG.Skip
+ ; local = Spec_forw.D.top () (*TODO: SOULD I GET THE VALUE FROM THE FORWARD INITIALIZATION?*)
+ ; global = (fun _ -> Spec_forw.G.bot ()) (*TODO: SHOULD I ALLOW TO ASK FOR GLOBALS?*)
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun _ _ -> failwith "forw_man in the backwards initialization should not be used to sideeffect globals.")
+ }
+ in
+ (* TODO: don't hd *)
+ List.hd (Spec_backw.threadenter man man_forw ~multiple:false None v [])
+ (* TODO: do threadspawn to mainfuns? *)
+ in
+ let prestartstate = Spec_backw.startstate MyCFG.dummy_func.svar in (* like in do_extern_inits *)
+ let othervars = List.map (enter_with (otherstate prestartstate)) otherfuns in
+ let startvars = List.concat (startvars @ exitvars @ othervars) in
+ if startvars = [] then
+ failwith "BUG: Empty set of start variables; may happen if enter_func of any analysis returns an empty list.";
+
+ AnalysisState.global_initialization := false;
+
+ (*
+ let man e =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in enter_with context.")
+ ; node = MyCFG.dummy_node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "enter_with has no control_context.")
+ ; context = Spec_forw.startcontext
+ ; edge = MyCFG.Skip
+ ; local = e
+ ; global = (fun g -> G_backw.spec (getg_backw (GV_backw.spec g)))
+ ; spawn = (fun ?(multiple=false) _ -> failwith "Bug1: Using enter_func for toplevel functions with 'otherstate'.")
+ ; split = (fun _ -> failwith "Bug2: Using enter_func for toplevel functions with 'otherstate'.")
+ ; sideg = (fun g d -> sideg_backw (GV_backw.spec g) d)
+ }
+ in
+ let startvars' = List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec_backw.context (man e) n e)) startvars in
+ let entrystates = List.map (fun (n,e) -> (MyCFG.Function n, Spec_backw.context (man e) n e), e) startvars in *)
+
+ (* Using dummy contexts which will be replaced by the contexts of the forward functions*)
+ let startvars' = List.map (fun (n,e) -> (MyCFG.FunctionEntry n, Spec_forw.startcontext ())) startvars in
+ let entrystates = List.map (fun (n,e) -> (MyCFG.Function n, Spec_forw.startcontext ()), e) startvars in
+
+ startvars', entrystates
+ in
+
+ calculate_startvars_backw ()
+ in
+
+ (** calculates and combines the solver input calculation from the forwards and backwards part of the constraint system. Returns [startvars'] and [entrystate] and [entrystates_global].*)
+ let calculate_solver_input () =
+ (* Spec_forw (MCP) initialization *)
+ AnalysisState.should_warn := PostSolverArg.should_warn;
+ Spec_forw.init None;
+ Access.init file;
+ AnalysisState.should_warn := false;
+
+
+ let entrystates_global = GHT.to_list gh in
+ let startvars'_forw, entrystates_forw = do_forward_inits () in
+ let startvars'_backw, entrystates_backw = do_backward_inits () in
+
+ (* Let's assume there is only one entrystate and startvar each. In what examples is this not the case?*)
+ let forward_context = match startvars'_forw with
+ | (_, ctx) :: _ -> ctx
+ | [] -> failwith "No startvars from forward analysis"
+ in
+ let startvars'_backw = List.map (fun (n, _) -> (n, forward_context)) startvars'_backw in
+ let entrystates_backw = List.map (fun ((n, _), d) -> ((n, forward_context), d)) entrystates_backw in
+
+ (* Lifting and combining the startvars and entrystates from forwards and backwards analysis*)
+ let startvars' = List. append (List.map (fun v -> `L_forw v) startvars'_forw) (List.map (fun v -> `L_backw v) startvars'_backw) in
+ (* let startvars' = List. append (List.map (fun v -> `L_backw v) startvars'_backw) (List.map (fun v -> `L_forw v) startvars'_forw) in *)
+ let entrystates = List.append (List.map (fun (v, d) -> (`L_forw v, `Lifted1 d)) entrystates_forw) (List.map (fun (v, d) -> (`L_backw v, `Lifted2 d)) entrystates_backw) in
+
+ startvars', entrystates, entrystates_global
+ in
+
+
+ (** solves constraint system*)
+ let solve () =
+ let solver_data = None in
+ let startvars', entrystates, entrystates_global = calculate_solver_input () in
+
+ let log_analysis_inputs () =
+ Logs.debug "================= Analysis Inputs ================";
+
+ (* Log entrystates *)
+ Logs.debug "--- Entry States (count: %d) ---" (List.length entrystates);
+ List.iteri (fun i (v, state) ->
+ Logs.debug "EntryState %d:" (i + 1);
+ Logs.debug " Var: %a" EQSys.LVar.pretty_trace v;
+ (* (match v with
+ | `L_forw (node, ctx)
+ | `L_backw (node, ctx) ->
+ Logs.debug " Node: %a" Node.pretty_trace node;
+ Logs.debug " Context: %a" Spec_forw.C.pretty ctx
+ ); *)
+ (match state with
+ | `Lifted1 d ->
+ Logs.debug " State: %a" Spec_forw.D.pretty d
+ | `Lifted2 d ->
+ Logs.debug " State: %a" Spec_backw.D.pretty d
+ | `Top ->
+ Logs.debug " State kind: Top";
+ | `Bot ->
+ Logs.debug " State kind: Bot"
+ );
+ ) entrystates;
+
+ (* Log entrystates_global *)
+ Logs.debug "--- Global Entry States (count: %d) ---" (List.length entrystates_global);
+ List.iteri (fun i (gvar, gstate) ->
+ Logs.debug "GlobalEntryState %d:" (i + 1);
+ Logs.debug " GVar: %a" EQSys.GVar.pretty_trace gvar;
+ Logs.debug " GState: %a" EQSys.G.pretty gstate;
+ ) entrystates_global;
+
+ (* Log startvars' *)
+ Logs.debug "--- Start Variables (count: %d) ---" (List.length startvars');
+ List.iteri (fun i v ->
+ Logs.debug "StartVar %d:" (i + 1);
+ Logs.debug " Var: %a" EQSys.LVar.pretty_trace v;
+ (* (match v with
+ | `L_forw (node, ctx)
+ | `L_backw (node, ctx) ->
+ Logs.debug " Node: %a" Node.pretty_trace node;
+ Logs.debug " Context: %a" Spec_forw.C.pretty ctx
+ ) *)
+ ) startvars';
+ Logs.debug "=============== End Analysis Inputs =============="
+ in
+ log_analysis_inputs ();
+
+ AnalysisState.should_warn := true;
+
+ let (lh, gh), solver_data = Slvr.solve entrystates entrystates_global startvars' solver_data in
+
+ let log_lh_contents lh =
+ let print_forw_entries : bool = false in
+ let print_backw_entries : bool = false in
+
+ if print_forw_entries || print_backw_entries then (
+
+ Logs.debug "================= LHT Contents ===================";
+ Logs.debug "LHT size: %d" (LHT.length lh);
+ let count = ref 0 in
+
+ Logs.debug "--- Full entry details ---";
+ LHT.iter (fun v state ->
+ incr count;
+ Logs.debug "Entry %d:" !count;
+ if (match v with `L_forw _ -> print_forw_entries | `L_backw _ -> print_backw_entries)
+ then (
+ Logs.debug " Var: %a" EQSys.LVar.pretty_trace v;
+ Logs.debug " Context: %a" Spec_forw.C.pretty (match v with
+ | `L_forw (_, ctx)
+ | `L_backw (_, ctx) -> ctx);
+ (match state with
+ | `Lifted1 d ->
+ (try
+ Logs.debug " State:%a" Spec_forw.D.pretty d
+ with e ->
+ Logs.debug " State: ERROR - %s" (Printexc.to_string e))
+ | `Lifted2 d ->
+ (try
+ Logs.debug " State: %a" Spec_backw.D.pretty d
+ with e ->
+ Logs.debug " State: ERROR - %s" (Printexc.to_string e)
+ );
+ | `Top ->
+ Logs.debug " State kind: Top";
+ | `Bot ->
+ Logs.debug " State kind: Bot"
+ );
+ ) else (
+ Logs.debug " (Entry skipped in log)"
+ )
+ )
+ lh;
+ Logs.debug "Total entries in LHT: %d" !count;
+ Logs.debug "=============== End LHT Contents =================";
+ ) else ();
+ in
+ log_lh_contents lh;
+
+ (* To check for unnacessary assigns, one has to take the join over all variables for that programm point*)
+ let warn_unnecessary_assignments () =
+ let post_backward_states_for_node (node: Node.t) : Spec_backw.D.t list =
+ let succ_nodes = List.map snd (Cfg.next node) in
+ LHT.fold (fun key state acc ->
+ match key, state with
+ | `L_backw (node', _), `Lifted2 d when List.exists (Node.equal node') succ_nodes -> d :: acc
+ | _ -> acc
+ ) lh []
+ in
+
+ let may_be_dead_assignment_in_state (node: Node.t) (state: Spec_backw.D.t) (lv: lval) : bool =
+ (* log *)
+ (* Logs.debug "Checking if assignment may be dead at node %a in state %a" Node.pretty_trace node Spec_backw.D.pretty state; *)
+
+ let man_backw =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in dead-assignment query helper.")
+ ; node = node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "dead-assignment query helper has no control_context.")
+ ; context = (fun () -> man_failwith "dead-assignment query helper has no context.")
+ ; edge = MyCFG.Skip
+ ; local = state
+ ; global = (fun _ -> Spec_backw.G.bot ())
+ ; spawn = (fun ?(multiple=false) _ -> failwith "dead-assignment query helper cannot spawn threads.")
+ ; split = (fun _ -> failwith "dead-assignment query helper cannot split paths.")
+ ; sideg = (fun _ _ -> failwith "dead-assignment query helper cannot side-effect globals.")
+ }
+ in
+ let man_forw =
+ { ask = (fun (type a) (q: a Queries.t) -> Queries.Result.top q)
+ ; emit = (fun _ -> failwith "Cannot \"emit\" in dead-assignment query helper.")
+ ; node = node
+ ; prev_node = MyCFG.dummy_node
+ ; control_context = (fun () -> man_failwith "dead-assignment query helper has no control_context.")
+ ; context = (fun () -> man_failwith "dead-assignment query helper has no context.")
+ ; edge = MyCFG.Skip
+ ; local = Spec_forw.D.top ()
+ ; global = (fun _ -> Spec_forw.G.bot ())
+ ; spawn = (fun ?(multiple=false) _ -> failwith "dead-assignment query helper cannot spawn threads.")
+ ; split = (fun _ -> failwith "dead-assignment query helper cannot split paths.")
+ ; sideg = (fun _ _ -> failwith "dead-assignment query helper cannot side-effect globals.")
+ }
+ in
+ Spec_backw.query man_backw man_forw (Queries.MayBeDeadAssignment lv)
+ in
+
+ let assigned_lvals_of_stmt (s: stmt) : lval list =
+ match s.skind with
+ | Instr instrs ->
+ List.fold_left (fun acc instr ->
+ match instr with
+ | Set (lv, _, _, _) -> lv :: acc
+ | Call (Some lv, _, _, _, _) -> lv :: acc
+ | _ -> acc
+ ) [] instrs
+ | _ -> []
+ in
+
+ let warn_assignment_stmt (s: stmt) =
+ let node = Statement s in
+ let assigned_lvals = assigned_lvals_of_stmt s in
+ match assigned_lvals with
+ | [] -> ()
+ | _ ->
+ let states = post_backward_states_for_node node in
+ if states <> [] then (
+ List.iter (fun lv ->
+ let dead_in_all_contexts =
+ List.for_all (fun st -> may_be_dead_assignment_in_state node st lv) states
+ in
+ if dead_in_all_contexts then
+ M.warn ~loc:(M.Location.Node node) ~category:MessageCategory.Program "Unnecessary assignment: this assignment may be dead in every post-assignment context."
+ ) assigned_lvals
+ )
+ in
+ List.iter (function
+ | GFun (fd, _) -> List.iter warn_assignment_stmt fd.sallstmts
+ | _ -> ()
+ ) file.globals
+ in
+ warn_unnecessary_assignments ();
+
+ let make_global_fast_xml f g =
+ let open Printf in
+ let print_globals k v =
+ fprintf f "\n%s%a" (XmlUtil.escape (EQSys.GVar.show k)) EQSys.G.printXml v;
+ in
+ GHT.iter print_globals g
+ in
+
+ let liveness _ = true in
+
+ let local_xml_forw = solver2source_result lh in
+
+ ResBundle_forw.ResultOutput.output (lazy local_xml_forw) liveness gh make_global_fast_xml (module FileCfg);
+
+ (*This is disgusting, but I have more important things to do right now.*)
+ let output_wp_results_to_xml lh =
+ (* iterate through all nodes and update corresponding .xml in result/nodes *)
+ LHT.iter (fun v state ->
+ match v with
+ | `L_forw _ -> ()
+ | `L_backw (node, c) -> (
+ let state = match state with
+ | `Lifted2 d -> d
+ | _ -> failwith "Expected backward state"
+ in
+ try
+ let node_id_str = Node.show_id node in
+
+ let xml_path = Filename.concat "./result/nodes" (node_id_str ^ ".xml") in
+ if Sys.file_exists xml_path then (
+ (* Read existing XML *)
+ let ic = Stdlib.open_in xml_path in
+ let content = Stdlib.really_input_string ic (Stdlib.in_channel_length ic) in
+ Stdlib.close_in ic;
+
+ (* Create WP analysis data *)
+ let wp_res = Pretty.sprint ~width:100 (Spec_backw.D.pretty () state) in
+ let wp_res_escaped = XmlUtil.escape wp_res in
+ let wp_data =
+ "\n\n\n\n" ^ wp_res_escaped ^" \n\n\n\n\n"
+ in
+
+ (* Insert before *)
+ let close_pattern = "" in
+ let updated_content =
+ try
+ let insert_pos = Str.search_backward (Str.regexp_string close_pattern) content (String.length content) in
+ let before = String.sub content 0 insert_pos in
+ let after = String.sub content insert_pos (String.length content - insert_pos) in
+ before ^ wp_data ^ after
+ with Not_found ->
+ content ^ wp_data
+ in
+
+ (* Write back *)
+ let oc = Stdlib.open_out xml_path in
+ Stdlib.output_string oc updated_content;
+ Stdlib.close_out oc;
+ (* Logs.debug "Updated XML file for node %s" node_id_str *)
+ )
+ with _ -> () (* Skip errors silently *)
+ )
+ ) lh
+ in
+ output_wp_results_to_xml lh;
+ in
+
+ solve();
+end
+
+(** This function was originally a part of the [AnalyzeCFG] module, but
+ now that [AnalyzeCFG] takes [Spec] as a functor parameter,
+ [analyze_loop] cannot reside in it anymore since each invocation of
+ [get_spec] in the loop might/should return a different module, and we
+ cannot swap the functor parameter from inside [AnalyzeCFG]. *)
let rec analyze_loop (module CFG : CfgBidirSkip) file fs change_info =
try
+
let (module Spec) = get_spec () in
- let module A = AnalyzeCFG (CFG) (Spec) (struct let increment = change_info end) in
- GobConfig.with_immutable_conf (fun () -> A.analyze file fs)
+
+ if (GobConfig.get_bool "ana.wp_run") then (
+ let module LivenesSpec = Liveness.BackwSpec in
+ let module A = AnalyzeCFG_bidir (CFG) (Spec) (LivenesSpec) (struct let increment = change_info end) in
+ GobConfig.with_immutable_conf (fun () -> A.analyze file fs)
+ ) else (
+ let module A = AnalyzeCFG (CFG) (Spec) (struct let increment = change_info end) in
+ GobConfig.with_immutable_conf (fun () -> A.analyze file fs)
+ )
+
with Refinement.RestartAnalysis ->
(* Tail-recursively restart the analysis again, when requested.
All solving starts from scratch.
diff --git a/tests/regression/99-tutorials/05-basic_liveness.c b/tests/regression/99-tutorials/05-basic_liveness.c
new file mode 100644
index 0000000000..6b886e91af
--- /dev/null
+++ b/tests/regression/99-tutorials/05-basic_liveness.c
@@ -0,0 +1,18 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+int main()
+{
+ int x = 1;
+ int y = 2;
+ int z = 3; // this assignment should yield a warning
+
+ int a = rand();
+
+ if (a) {
+ x = x + y;
+ }
+
+ return x;
+}
+
diff --git a/tests/regression/99-tutorials/06-forward_branch_info.c b/tests/regression/99-tutorials/06-forward_branch_info.c
new file mode 100644
index 0000000000..b5a377c7f4
--- /dev/null
+++ b/tests/regression/99-tutorials/06-forward_branch_info.c
@@ -0,0 +1,14 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int main()
+{
+ int x = 1;
+ int y = 2; // this assignment should yield a warning, as the path where y is used is never taken
+ int z = 0;
+
+ if (z) {
+ x = x + y;
+ }
+
+ return x;
+}
diff --git a/tests/regression/99-tutorials/07-basic_function_call.c b/tests/regression/99-tutorials/07-basic_function_call.c
new file mode 100644
index 0000000000..99d6702f96
--- /dev/null
+++ b/tests/regression/99-tutorials/07-basic_function_call.c
@@ -0,0 +1,21 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int f(int a, int b) {
+
+ if (a < 0) {
+ return a + b;
+ } else {
+ return a;
+ }
+
+}
+
+int main()
+{
+ int x = 1;
+ int y = 2; // this assignment should yield a warning, as y is not used in the path taken in the called function
+
+ int z = f(x, y);
+
+ return z;
+}
diff --git a/tests/regression/99-tutorials/08-function_pointer_resolve.c b/tests/regression/99-tutorials/08-function_pointer_resolve.c
new file mode 100644
index 0000000000..ba7a3fcafe
--- /dev/null
+++ b/tests/regression/99-tutorials/08-function_pointer_resolve.c
@@ -0,0 +1,23 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int f(int a, int b) {
+
+ if (a < 0) {
+ return a + b;
+ } else {
+ return a;
+ }
+
+}
+
+int main()
+{
+ int x = 1;
+ int y = 2; // this assignment should yield a warning, as y is not used in the path taken in the called function
+
+ int (*h) (int, int) = &f;
+
+ int z = h(x, y);
+
+ return z;
+}
diff --git a/tests/regression/99-tutorials/09-ambigious_function_pointer.c b/tests/regression/99-tutorials/09-ambigious_function_pointer.c
new file mode 100644
index 0000000000..976a1c1ecf
--- /dev/null
+++ b/tests/regression/99-tutorials/09-ambigious_function_pointer.c
@@ -0,0 +1,41 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+int f(int a, int b) {
+
+ if (a > 0) {
+ return a + b;
+ } else {
+ return a;
+ }
+
+}
+
+int g(int a, int b) {
+
+ if (a < 0) {
+ return a - b;
+ } else {
+ return a;
+ }
+
+}
+
+int main()
+{
+ int x = 1;
+ int y = 2;
+
+ int (*h) (int, int) = &f;
+
+ int c = rand();
+ if (c) {
+ h = &g;
+ }
+
+ int z = h(x, y);
+
+ return z;
+}
+
+// no warnings here, since we cannot determine which function is called and y is used if h evaluates to f, so we have to assume that y is used
\ No newline at end of file
diff --git a/tests/regression/99-tutorials/10-function_call_complex_attributes.c b/tests/regression/99-tutorials/10-function_call_complex_attributes.c
new file mode 100644
index 0000000000..a838d779ce
--- /dev/null
+++ b/tests/regression/99-tutorials/10-function_call_complex_attributes.c
@@ -0,0 +1,25 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+int f(int a, int b) {
+
+ if (a > 0) {
+ return a + b;
+ } else {
+ return a;
+ }
+
+}
+
+
+int main()
+{
+ int x = 1;
+ int y = 2;
+
+ int z = f(x + (y * y), 0);
+
+ return z;
+}
+
+// now warnings here. Both variables are relevant as they are used in an expression passed to a live parameter of the function call.
\ No newline at end of file
diff --git a/tests/regression/99-tutorials/11-function_call_return_val_mem.c b/tests/regression/99-tutorials/11-function_call_return_val_mem.c
new file mode 100644
index 0000000000..0bb813110f
--- /dev/null
+++ b/tests/regression/99-tutorials/11-function_call_return_val_mem.c
@@ -0,0 +1,24 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+int f(int a, int b) {
+
+ if (a > 0) {
+ return a + b;
+ } else {
+ return a;
+ }
+
+}
+
+int main()
+{
+ int i = 1;
+ int x = -1;
+ int y = 2;
+ int *p = malloc(sizeof(int) * i);
+
+ p[0] = f(x, y);
+
+ return x;
+}
diff --git a/tests/regression/99-tutorials/12-memory_write_in_call.c b/tests/regression/99-tutorials/12-memory_write_in_call.c
new file mode 100644
index 0000000000..a6eeef997d
--- /dev/null
+++ b/tests/regression/99-tutorials/12-memory_write_in_call.c
@@ -0,0 +1,18 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+int f(int a, int* b) {
+ b[0] = a ;
+ return 0;
+}
+
+
+int main()
+{
+ int x = 1;
+ int *p = malloc(sizeof(int));
+
+ f(x, p);
+
+ return x;
+}
diff --git a/tests/regression/99-tutorials/13-pointer_used.c b/tests/regression/99-tutorials/13-pointer_used.c
new file mode 100644
index 0000000000..e40d102982
--- /dev/null
+++ b/tests/regression/99-tutorials/13-pointer_used.c
@@ -0,0 +1,19 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+#include
+
+
+int main()
+{
+ int x = 1;
+ int y = 2;
+
+ int *p = &x;
+
+ *p = 3; // x is now 3 and should not be live before this point anymore
+ int z = 2 * (*p);
+
+ return z;
+}
+
+/* This only works if we assume that if the query MayPointTo returns a single variable, then this variable is definietely the one pointed to.
+ */
\ No newline at end of file
diff --git a/tests/regression/99-tutorials/14-motivating_example.c b/tests/regression/99-tutorials/14-motivating_example.c
new file mode 100644
index 0000000000..d3f1196972
--- /dev/null
+++ b/tests/regression/99-tutorials/14-motivating_example.c
@@ -0,0 +1,31 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int f(int a, int b)
+{
+ return a + b;
+}
+
+int g(int a, int b)
+{
+ if (a > 0) {
+ return a - b;
+ } else {
+ return a;
+ }
+}
+
+int main()
+{
+ int x = 1;
+ int y = 2;
+ int *c = &x;
+ int (*h) (int, int) = &f;
+
+ if (*c) {
+ h = &g;
+ }
+
+ *c = 0;
+ int z = h(x, y);
+ return z;
+}
diff --git a/tests/regression/99-tutorials/15-same_proc_different_results.c b/tests/regression/99-tutorials/15-same_proc_different_results.c
new file mode 100644
index 0000000000..4967a2c1b1
--- /dev/null
+++ b/tests/regression/99-tutorials/15-same_proc_different_results.c
@@ -0,0 +1,24 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int f(int a)
+{
+ int b = 3; // no warning, as b is used in one call of f
+
+ if (a < 5) {
+ return a + b;
+ } else {
+ return a;
+ }
+}
+
+int main()
+{
+ int x = 0;
+
+ int u = f (x);
+
+ x = 10;
+
+ int v = f (x);
+ return u + v;
+}
diff --git a/tests/regression/99-tutorials/16-global_variables.c b/tests/regression/99-tutorials/16-global_variables.c
new file mode 100644
index 0000000000..c26763d2d8
--- /dev/null
+++ b/tests/regression/99-tutorials/16-global_variables.c
@@ -0,0 +1,14 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+int x = 0;
+
+int f()
+{
+ x ++;
+}
+
+int main()
+{
+ f();
+ return 0;
+}
diff --git a/tests/regression/99-tutorials/17-global_func_pointer_mod.c b/tests/regression/99-tutorials/17-global_func_pointer_mod.c
new file mode 100644
index 0000000000..b33f06422a
--- /dev/null
+++ b/tests/regression/99-tutorials/17-global_func_pointer_mod.c
@@ -0,0 +1,23 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+int (*h) (int);
+
+int g(int a)
+{
+ return 0;
+}
+
+int f(int a)
+{
+ h = &g;
+ return a;
+}
+
+int main()
+{
+ int x = 1;
+ h = &f;
+
+ int y = h(x);
+
+ return y;
+}
diff --git a/tests/regression/99-tutorials/18-pointer_to_partial_obj.c b/tests/regression/99-tutorials/18-pointer_to_partial_obj.c
new file mode 100644
index 0000000000..eb63006c90
--- /dev/null
+++ b/tests/regression/99-tutorials/18-pointer_to_partial_obj.c
@@ -0,0 +1,8 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+int main()
+{
+ int x[] = {1, 2, 3};
+ x [0] = 0;
+
+ return x[1];
+}
diff --git a/tests/regression/99-tutorials/19-struct_partial_update.c b/tests/regression/99-tutorials/19-struct_partial_update.c
new file mode 100644
index 0000000000..69c14d3c46
--- /dev/null
+++ b/tests/regression/99-tutorials/19-struct_partial_update.c
@@ -0,0 +1,14 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+struct Pair {
+ int a;
+ int b;
+};
+
+int main()
+{
+ struct Pair p;
+ p.b = 42;
+ p.a = 1;
+ return p.b;
+}
diff --git a/tests/regression/99-tutorials/20-struct_ptr_partial_update.c b/tests/regression/99-tutorials/20-struct_ptr_partial_update.c
new file mode 100644
index 0000000000..d916c19d60
--- /dev/null
+++ b/tests/regression/99-tutorials/20-struct_ptr_partial_update.c
@@ -0,0 +1,17 @@
+// SKIP TERM PARAM: --enable ana.wp_run
+
+struct Pair {
+ int a;
+ int b;
+};
+
+int main()
+{
+ struct Pair p;
+ struct Pair *pp = &p;
+
+ pp->b = 42;
+ pp->a = 1;
+
+ return p.b;
+}
diff --git a/xslt/node.xsl b/xslt/node.xsl
index bb1ddcabfd..7d580adc79 100644
--- a/xslt/node.xsl
+++ b/xslt/node.xsl
@@ -101,6 +101,27 @@
+
+
+
+
+
+
+
+ wp_path:
+
+
+
+
+
+
+
+
../frame.html?file=&fun=&node=
@@ -118,6 +139,7 @@
+
diff --git a/xy_easyprog.c b/xy_easyprog.c
new file mode 100644
index 0000000000..af09bd62e8
--- /dev/null
+++ b/xy_easyprog.c
@@ -0,0 +1,48 @@
+#include
+
+int f(int x, int y) {
+ int i = 2;
+
+ if (x > 0) {
+ i = i + 2;
+ return i + y;
+ } else {
+ i = i + 3;
+ return i + x;
+ }
+}
+
+int g (int x, int y) {
+ int i = 2;
+
+ if (x > 0) {
+ i = i + 2;
+ return i;
+ } else {
+ i = i + 3;
+ return i + x;
+ }
+}
+
+
+int main() {
+ int a = 0;
+ int c = 3;
+
+ int rand;
+
+ int (*h)(int, int); // function pointer to f
+ h = &f;
+
+ // if (rand) {
+ // h = &g;
+ // }
+ int d = f (a, c);
+
+ // a = -100;
+
+ // int b = (*h)(a, c);
+ return d;
+}
+
+//git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ml|mli)$' | xargs -I {} ocp-indent -i {}
\ No newline at end of file