[hil] functor for easily creating HIL analyses

Summary:
Last step for converting thread-safety and quandary to HIL.
Push the logic for managing the id map and converting the instructions into a functor.
This way, client analyses can simply write HIL transfer functions and call the functor.

Reviewed By: jberdine

Differential Revision: D4989987

fbshipit-source-id: 485169e
master
Sam Blackshear 8 years ago committed by Facebook Github Bot
parent 1a141eddca
commit 19da59cf19

@ -14,7 +14,7 @@ module L = Logging
type 'a state = { pre: 'a; post: 'a; visit_count: int; } type 'a state = { pre: 'a; post: 'a; visit_count: int; }
module type S = sig module type S = sig
module TransferFunctions : TransferFunctions.S module TransferFunctions : TransferFunctions.SIL
module InvariantMap : Caml.Map.S with type key = TransferFunctions.CFG.id module InvariantMap : Caml.Map.S with type key = TransferFunctions.CFG.id
@ -44,7 +44,7 @@ end
module MakeNoCFG module MakeNoCFG
(Scheduler : Scheduler.S) (Scheduler : Scheduler.S)
(TransferFunctions : TransferFunctions.S with module CFG = Scheduler.CFG) = struct (TransferFunctions : TransferFunctions.SIL with module CFG = Scheduler.CFG) = struct
module CFG = Scheduler.CFG module CFG = Scheduler.CFG
module InvariantMap = ProcCfg.NodeIdMap (CFG) module InvariantMap = ProcCfg.NodeIdMap (CFG)
@ -167,8 +167,8 @@ module Interprocedural (Summ : Summary.S) = struct
end end
module MakeWithScheduler (C : ProcCfg.S) (S : Scheduler.Make) (T : TransferFunctions.Make) = module MakeWithScheduler (C : ProcCfg.S) (S : Scheduler.Make) (T : TransferFunctions.MakeSIL) =
MakeNoCFG (S (C)) (T (C)) MakeNoCFG (S (C)) (T (C))
module Make (C : ProcCfg.S) (T : TransferFunctions.Make) = module Make (C : ProcCfg.S) (T : TransferFunctions.MakeSIL) =
MakeWithScheduler (C) (Scheduler.ReversePostorder) (T) MakeWithScheduler (C) (Scheduler.ReversePostorder) (T)

@ -13,7 +13,7 @@ type 'a state = { pre: 'a; post: 'a; visit_count: int; }
(** type of an intraprocedural abstract interpreter *) (** type of an intraprocedural abstract interpreter *)
module type S = sig module type S = sig
module TransferFunctions : TransferFunctions.S module TransferFunctions : TransferFunctions.SIL
module InvariantMap : Caml.Map.S with type key = TransferFunctions.CFG.id module InvariantMap : Caml.Map.S with type key = TransferFunctions.CFG.id
@ -52,14 +52,14 @@ end
(** create an intraprocedural abstract interpreter from a scheduler and transfer functions *) (** create an intraprocedural abstract interpreter from a scheduler and transfer functions *)
module MakeNoCFG module MakeNoCFG
(Scheduler : Scheduler.S) (Scheduler : Scheduler.S)
(TransferFunctions : TransferFunctions.S with module CFG = Scheduler.CFG) : (TransferFunctions : TransferFunctions.SIL with module CFG = Scheduler.CFG) :
S with module TransferFunctions = TransferFunctions S with module TransferFunctions = TransferFunctions
(** create an intraprocedural abstract interpreter from a CFG and functors for creating a scheduler/ (** create an intraprocedural abstract interpreter from a CFG and functors for creating a scheduler/
transfer functions from a CFG *) transfer functions from a CFG *)
module Make module Make
(CFG : ProcCfg.S) (CFG : ProcCfg.S)
(MakeTransferFunctions : TransferFunctions.Make) : (MakeTransferFunctions : TransferFunctions.MakeSIL) :
S with module TransferFunctions = MakeTransferFunctions(CFG) S with module TransferFunctions = MakeTransferFunctions(CFG)
(** create an interprocedural abstract interpreter given logic for handling summaries *) (** create an interprocedural abstract interpreter given logic for handling summaries *)

@ -0,0 +1,42 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
module Make (MakeTransferFunctions : TransferFunctions.MakeHIL) (CFG : ProcCfg.S) = struct
module TransferFunctions = MakeTransferFunctions (CFG)
module CFG = TransferFunctions.CFG
module Domain = AbstractDomain.Pair (TransferFunctions.Domain) (IdAccessPathMapDomain)
type extras = TransferFunctions.extras
let exec_instr ((actual_state, id_map) as astate) extras node instr =
let f_resolve_id id =
try Some (IdAccessPathMapDomain.find id id_map)
with Not_found -> None in
match HilInstr.of_sil ~f_resolve_id instr with
| Bind (id, access_path) ->
let id_map' = IdAccessPathMapDomain.add id access_path id_map in
if phys_equal id_map id_map'
then astate
else actual_state, id_map'
| Unbind ids ->
let id_map' =
List.fold
~f:(fun acc id -> IdAccessPathMapDomain.remove id acc) ~init:id_map ids in
if phys_equal id_map id_map'
then astate
else actual_state, id_map'
| Instr hil_instr ->
let actual_state' = TransferFunctions.exec_instr actual_state extras node hil_instr in
if phys_equal actual_state actual_state'
then astate
else actual_state', id_map
| Ignore ->
astate
end

@ -0,0 +1,24 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
(** Functor for turning HIL transfer functions into SIL transfer functions *)
module Make (MakeTransferFunctions : TransferFunctions.MakeHIL) (CFG : ProcCfg.S) : sig
module TransferFunctions : module type of (MakeTransferFunctions (CFG))
module CFG : module type of TransferFunctions.CFG
module Domain :
module type of AbstractDomain.Pair (TransferFunctions.Domain) (IdAccessPathMapDomain)
type extras = TransferFunctions.extras
val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Domain.astate
end

@ -384,295 +384,283 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| _ -> | _ ->
false false
let exec_instr (astate : Domain.astate) ({ ProcData.extras; tenv; pdesc; } as proc_data) _ instr = let add_reads exps loc accesses locks attribute_map proc_data =
List.fold
~f:(fun acc exp -> add_access exp loc Read acc locks attribute_map proc_data)
exps
~init:accesses
let exec_instr
(astate : Domain.astate)
({ ProcData.extras; tenv; pdesc; } as proc_data)
_
(instr : HilInstr.t) =
let open Domain in let open Domain in
let add_reads exps loc accesses locks attribute_map proc_data = match instr with
List.fold | Call (Some ret_base, Direct procname, actuals, _, loc)
~f:(fun acc exp -> add_access exp loc Read acc locks attribute_map proc_data) when acquires_ownership procname tenv ->
exps let accesses =
~init:accesses in add_reads actuals loc astate.accesses astate.locks astate.attribute_map proc_data in
let exec_instr_ = function let attribute_map =
| HilInstr.Call (Some ret_base, Direct procname, actuals, _, loc) AttributeMapDomain.add_attribute
when acquires_ownership procname tenv -> (ret_base, []) Attribute.unconditionally_owned astate.attribute_map in
let accesses = { astate with accesses; attribute_map; }
add_reads actuals loc astate.accesses astate.locks astate.attribute_map proc_data in
let attribute_map = | Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) ->
AttributeMapDomain.add_attribute let accesses =
(ret_base, []) Attribute.unconditionally_owned astate.attribute_map in add_reads actuals loc astate.accesses astate.locks astate.attribute_map proc_data in
{ astate with accesses; attribute_map; } let astate = { astate with accesses; } in
let astate_callee =
| HilInstr.Call (ret_opt, Direct callee_pname, actuals, call_flags, loc) -> (* assuming that modeled procedures do not have useful summaries *)
let accesses = if is_thread_utils_method "assertMainThread" callee_pname
add_reads actuals loc astate.accesses astate.locks astate.attribute_map proc_data in then
let astate = { astate with accesses; } in { astate with threads = true; }
let astate_callee = else
(* assuming that modeled procedures do not have useful summaries *) match get_lock_model callee_pname with
if is_thread_utils_method "assertMainThread" callee_pname | Lock ->
then { astate with locks = true; }
{ astate with threads = true; } | Unlock ->
else { astate with locks = false; }
match get_lock_model callee_pname with | LockedIfTrue ->
| Lock -> begin
{ astate with locks = true; } match ret_opt with
| Unlock -> | Some ret_access_path ->
{ astate with locks = false; }
| LockedIfTrue ->
begin
match ret_opt with
| Some ret_access_path ->
let attribute_map =
AttributeMapDomain.add_attribute
(ret_access_path, [])
(Choice Choice.LockHeld)
astate.attribute_map in
{ astate with attribute_map; }
| None ->
failwithf
"Procedure %a specified as returning boolean, but returns nothing"
Typ.Procname.pp callee_pname
end
| NoEffect ->
match get_summary pdesc callee_pname actuals loc tenv with
| Some (callee_threads, callee_locks, callee_accesses, return_attributes) ->
let update_caller_accesses pre callee_accesses caller_accesses =
let combined_accesses =
PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc)
|> PathDomain.join (AccessDomain.get_accesses pre caller_accesses) in
AccessDomain.add pre combined_accesses caller_accesses in
let locks = callee_locks || astate.locks in
let threads = callee_threads || astate.threads in
let unprotected = is_unprotected locks pdesc in
(* add [ownership_accesses] to the [accesses_acc] with a protected pre if
[exp] is owned, and an appropriate unprotected pre otherwise *)
let add_ownership_access ownership_accesses actual_exp accesses_acc =
match actual_exp with
| HilExp.Constant _ ->
(* the actual is a constant, so it's owned in the caller. *)
accesses_acc
| HilExp.AccessPath actual_access_path ->
if is_owned actual_access_path astate.attribute_map
then
(* the actual passed to the current callee is owned. drop all the
conditional accesses for that actual, since they're all safe *)
accesses_acc
else
let pre =
if unprotected
then
let base = fst actual_access_path in
match FormalMap.get_formal_index base extras with
| Some formal_index ->
(* the actual passed to the current callee is rooted in a
formal *)
AccessPrecondition.Unprotected (Some formal_index)
| None ->
match
AttributeMapDomain.get_conditional_ownership_index
actual_access_path
astate.attribute_map
with
| Some formal_index ->
(* access path conditionally owned if [formal_index] is
owned *)
AccessPrecondition.Unprotected (Some formal_index)
| None ->
(* access path not rooted in a formal and not
conditionally owned *)
AccessPrecondition.unprotected
else
(* access protected by held lock *)
AccessPrecondition.Protected in
update_caller_accesses pre ownership_accesses accesses_acc
| _ ->
(* couldn't find access path, don't know if it's owned *)
update_caller_accesses
AccessPrecondition.unprotected ownership_accesses accesses_acc in
let accesses =
let update_accesses pre callee_accesses accesses_acc = match pre with
| AccessPrecondition.Protected ->
update_caller_accesses pre callee_accesses accesses_acc
| AccessPrecondition.Unprotected None ->
let pre' =
if unprotected
then pre
else AccessPrecondition.Protected in
update_caller_accesses pre' callee_accesses accesses_acc
| AccessPrecondition.Unprotected (Some index) ->
add_ownership_access
callee_accesses (List.nth_exn actuals index) accesses_acc in
AccessDomain.fold update_accesses callee_accesses astate.accesses in
let attribute_map = let attribute_map =
propagate_return_attributes AttributeMapDomain.add_attribute
ret_opt (ret_access_path, [])
return_attributes (Choice Choice.LockHeld)
actuals astate.attribute_map in
astate.attribute_map { astate with attribute_map; }
extras in
{ astate with locks; threads; accesses; attribute_map; }
| None -> | None ->
let should_assume_returns_ownership (call_flags : CallFlags.t) actuals = failwithf
(* assume non-interface methods with no summary and no parameters return "Procedure %a specified as returning boolean, but returns nothing"
ownership *) Typ.Procname.pp callee_pname
not (call_flags.cf_interface) && List.is_empty actuals in end
if is_box callee_pname | NoEffect ->
then match get_summary pdesc callee_pname actuals loc tenv with
match ret_opt, actuals with | Some (callee_threads, callee_locks, callee_accesses, return_attributes) ->
| Some ret, HilExp.AccessPath actual_ap :: _ let update_caller_accesses pre callee_accesses caller_accesses =
when AttributeMapDomain.has_attribute let combined_accesses =
actual_ap Functional astate.attribute_map -> PathDomain.with_callsite callee_accesses (CallSite.make callee_pname loc)
(* TODO: check for constants, which are functional? *) |> PathDomain.join (AccessDomain.get_accesses pre caller_accesses) in
let attribute_map = AccessDomain.add pre combined_accesses caller_accesses in
AttributeMapDomain.add_attribute let locks = callee_locks || astate.locks in
(ret, []) let threads = callee_threads || astate.threads in
Functional let unprotected = is_unprotected locks pdesc in
astate.attribute_map in (* add [ownership_accesses] to the [accesses_acc] with a protected pre if
{ astate with attribute_map; } [exp] is owned, and an appropriate unprotected pre otherwise *)
| _ -> let add_ownership_access ownership_accesses actual_exp accesses_acc =
astate match actual_exp with
else if should_assume_returns_ownership call_flags actuals | HilExp.Constant _ ->
then (* the actual is a constant, so it's owned in the caller. *)
match ret_opt with accesses_acc
| Some ret -> | HilExp.AccessPath actual_access_path ->
let attribute_map = if is_owned actual_access_path astate.attribute_map
AttributeMapDomain.add_attribute then
(ret, []) (* the actual passed to the current callee is owned. drop all the
Attribute.unconditionally_owned conditional accesses for that actual, since they're all safe *)
astate.attribute_map in accesses_acc
{ astate with attribute_map; } else
| None -> let pre =
astate if unprotected
else then
astate in let base = fst actual_access_path in
begin match FormalMap.get_formal_index base extras with
match ret_opt with | Some formal_index ->
| Some (_, { Typ.desc=Typ.Tint ILong | Tfloat FDouble }) -> (* the actual passed to the current callee is rooted in a
(* writes to longs and doubles are not guaranteed to be atomic in Java, so don't formal *)
bother tracking whether a returned long or float value is functional *) AccessPrecondition.Unprotected (Some formal_index)
astate_callee | None ->
| Some ret -> match
let add_if_annotated predicate attribute attribute_map = AttributeMapDomain.get_conditional_ownership_index
if PatternMatch.override_exists predicate tenv callee_pname actual_access_path
then astate.attribute_map
AttributeMapDomain.add_attribute (ret, []) attribute attribute_map with
else | Some formal_index ->
attribute_map in (* access path conditionally owned if [formal_index] is
let attribute_map = owned *)
add_if_annotated is_functional Functional astate_callee.attribute_map AccessPrecondition.Unprotected (Some formal_index)
|> add_if_annotated | None ->
(has_return_annot Annotations.ia_is_returns_ownership) (* access path not rooted in a formal and not
Domain.Attribute.unconditionally_owned in conditionally owned *)
{ astate_callee with attribute_map; } AccessPrecondition.unprotected
| _ -> else
astate_callee (* access protected by held lock *)
end AccessPrecondition.Protected in
update_caller_accesses pre ownership_accesses accesses_acc
| _ ->
(* couldn't find access path, don't know if it's owned *)
update_caller_accesses
AccessPrecondition.unprotected ownership_accesses accesses_acc in
let accesses =
let update_accesses pre callee_accesses accesses_acc = match pre with
| AccessPrecondition.Protected ->
update_caller_accesses pre callee_accesses accesses_acc
| AccessPrecondition.Unprotected None ->
let pre' =
if unprotected
then pre
else AccessPrecondition.Protected in
update_caller_accesses pre' callee_accesses accesses_acc
| AccessPrecondition.Unprotected (Some index) ->
add_ownership_access
callee_accesses (List.nth_exn actuals index) accesses_acc in
AccessDomain.fold update_accesses callee_accesses astate.accesses in
let attribute_map =
propagate_return_attributes
ret_opt
return_attributes
actuals
astate.attribute_map
extras in
{ locks; threads; accesses; attribute_map; }
| None ->
let should_assume_returns_ownership (call_flags : CallFlags.t) actuals =
(* assume non-interface methods with no summary and no parameters return
ownership *)
not (call_flags.cf_interface) && List.is_empty actuals in
if is_box callee_pname
then
match ret_opt, actuals with
| Some ret, HilExp.AccessPath actual_ap :: _
when AttributeMapDomain.has_attribute
actual_ap Functional astate.attribute_map ->
(* TODO: check for constants, which are functional? *)
let attribute_map =
AttributeMapDomain.add_attribute
(ret, [])
Functional
astate.attribute_map in
{ astate with attribute_map; }
| _ ->
astate
else if should_assume_returns_ownership call_flags actuals
then
match ret_opt with
| Some ret ->
let attribute_map =
AttributeMapDomain.add_attribute
(ret, [])
Attribute.unconditionally_owned
astate.attribute_map in
{ astate with attribute_map; }
| None ->
astate
else
astate in
begin
match ret_opt with
| Some (_, { Typ.desc=Typ.Tint ILong | Tfloat FDouble }) ->
(* writes to longs and doubles are not guaranteed to be atomic in Java, so don't
bother tracking whether a returned long or float value is functional *)
astate_callee
| Some ret ->
let add_if_annotated predicate attribute attribute_map =
if PatternMatch.override_exists predicate tenv callee_pname
then
AttributeMapDomain.add_attribute (ret, []) attribute attribute_map
else
attribute_map in
let attribute_map =
add_if_annotated is_functional Functional astate_callee.attribute_map
|> add_if_annotated
(has_return_annot Annotations.ia_is_returns_ownership)
Domain.Attribute.unconditionally_owned in
{ astate_callee with attribute_map; }
| _ ->
astate_callee
end
| HilInstr.Write (lhs_access_path, rhs_exp, loc) -> | Write (lhs_access_path, rhs_exp, loc) ->
let rhs_accesses = let rhs_accesses =
add_access
rhs_exp loc Read astate.accesses astate.locks astate.attribute_map proc_data in
let rhs_access_paths = HilExp.get_access_paths rhs_exp in
let is_functional =
not (List.is_empty rhs_access_paths) &&
List.for_all
~f:(fun access_path ->
AttributeMapDomain.has_attribute access_path Functional astate.attribute_map)
rhs_access_paths in
let accesses =
if is_functional
then
(* we want to forget about writes to @Functional fields altogether, otherwise we'll
report spurious read/write races *)
rhs_accesses
else
add_access add_access
rhs_exp loc Read astate.accesses astate.locks astate.attribute_map proc_data in (AccessPath lhs_access_path)
let rhs_access_paths = HilExp.get_access_paths rhs_exp in loc
let is_functional = Write
not (List.is_empty rhs_access_paths) &&
List.for_all
~f:(fun access_path ->
AttributeMapDomain.has_attribute access_path Functional astate.attribute_map)
rhs_access_paths in
let accesses =
if is_functional
then
(* we want to forget about writes to @Functional fields altogether, otherwise we'll
report spurious read/write races *)
rhs_accesses rhs_accesses
else astate.locks
add_access astate.attribute_map
(AccessPath lhs_access_path) proc_data in
loc let attribute_map =
Write propagate_attributes
rhs_accesses lhs_access_path (HilExp.get_access_paths rhs_exp) astate.attribute_map extras in
astate.locks { astate with accesses; attribute_map; }
astate.attribute_map
proc_data in | Assume (assume_exp, _, _, loc) ->
let attribute_map = let rec eval_binop op var e1 e2 =
propagate_attributes match eval_bexp var e1, eval_bexp var e2 with
lhs_access_path (HilExp.get_access_paths rhs_exp) astate.attribute_map extras in | Some b1, Some b2 -> Some (op b1 b2)
{ astate with accesses; attribute_map; } | _ -> None
(* return Some bool_value if the given boolean expression evaluates to bool_value when
| HilInstr.Assume (assume_exp, _, _, loc) -> [var] is set to true. return None if it has free variables that stop us from
let rec eval_binop op var e1 e2 = evaluating it *)
match eval_bexp var e1, eval_bexp var e2 with and eval_bexp var = function
| Some b1, Some b2 -> Some (op b1 b2) | HilExp.AccessPath ap when AccessPath.Raw.equal ap var ->
| _ -> None Some true
(* return Some bool_value if the given boolean expression evaluates to bool_value when | HilExp.Constant c ->
[var] is set to true. return None if it has free variables that stop us from Some (not (Const.iszero_int_float c))
evaluating it *) | HilExp.UnaryOperator (Unop.LNot, e, _) ->
and eval_bexp var = function let b_opt = eval_bexp var e in
| HilExp.AccessPath ap when AccessPath.Raw.equal ap var -> Option.map ~f:not b_opt
Some true | HilExp.BinaryOperator (Binop.LAnd, e1, e2) ->
| HilExp.Constant c -> eval_binop (&&) var e1 e2
Some (not (Const.iszero_int_float c)) | HilExp.BinaryOperator (Binop.LOr, e1, e2) ->
| HilExp.UnaryOperator (Unop.LNot, e, _) -> eval_binop (||) var e1 e2
let b_opt = eval_bexp var e in | HilExp.BinaryOperator (Binop.Eq, e1, e2) ->
Option.map ~f:not b_opt eval_binop Bool.equal var e1 e2
| HilExp.BinaryOperator (Binop.LAnd, e1, e2) -> | HilExp.BinaryOperator (Binop.Ne, e1, e2) ->
eval_binop (&&) var e1 e2 eval_binop (<>) var e1 e2
| HilExp.BinaryOperator (Binop.LOr, e1, e2) -> | _ ->
eval_binop (||) var e1 e2 (* non-boolean expression; can't evaluate it *)
| HilExp.BinaryOperator (Binop.Eq, e1, e2) -> None in
eval_binop Bool.equal var e1 e2 let add_choice bool_value acc = function
| HilExp.BinaryOperator (Binop.Ne, e1, e2) -> | Choice.LockHeld ->
eval_binop (<>) var e1 e2 let locks = bool_value in
| _ -> { acc with locks; }
(* non-boolean expression; can't evaluate it *) | Choice.OnMainThread ->
None in let threads = bool_value in
let add_choice bool_value acc = function { acc with threads; } in
| Choice.LockHeld ->
let locks = bool_value in let accesses =
{ acc with locks; } add_access
| Choice.OnMainThread -> assume_exp loc Read astate.accesses astate.locks astate.attribute_map proc_data in
let threads = bool_value in let astate' =
{ acc with threads; } in match HilExp.get_access_paths assume_exp with
| [access_path] ->
let accesses = let choices = AttributeMapDomain.get_choices access_path astate.attribute_map in
add_access begin
assume_exp loc Read astate.accesses astate.locks astate.attribute_map proc_data in match eval_bexp access_path assume_exp with
let astate' = | Some bool_value ->
match HilExp.get_access_paths assume_exp with (* prune (prune_exp) can only evaluate to true if the choice is [bool_value].
| [access_path] -> add the constraint that the the choice must be [bool_value] to the state *)
let choices = AttributeMapDomain.get_choices access_path astate.attribute_map in List.fold ~f:(add_choice bool_value) ~init:astate choices
begin | None ->
match eval_bexp access_path assume_exp with astate
| Some bool_value -> end
(* prune (prune_exp) can only evaluate to true if the choice is [bool_value]. | _ ->
add the constraint that the the choice must be [bool_value] to the state *) astate in
List.fold ~f:(add_choice bool_value) ~init:astate choices { astate' with accesses; }
| None -> | Call (_, Indirect _, _, _, _) ->
astate failwithf "Unexpected indirect call instruction %a" HilInstr.pp instr
end
| _ ->
astate in
{ astate' with accesses; }
| (HilInstr.Call (_, Indirect _, _, _, _) as hil_instr) ->
failwithf "Unexpected indirect call instruction %a" HilInstr.pp hil_instr in
let f_resolve_id id =
try Some (IdAccessPathMapDomain.find id astate.id_map)
with Not_found -> None in
match HilInstr.of_sil ~f_resolve_id instr with
| Bind (id, access_path) ->
let id_map = IdAccessPathMapDomain.add id access_path astate.id_map in
{ astate with id_map; }
| Unbind ids ->
let id_map =
List.fold
~f:(fun acc id -> IdAccessPathMapDomain.remove id acc) ~init:astate.id_map ids in
{ astate with id_map; }
| Instr hil_instr ->
exec_instr_ hil_instr
| Ignore ->
astate
end end
module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (TransferFunctions) module Analyzer = AbstractInterpreter.Make (ProcCfg.Normal) (LowerHil.Make(TransferFunctions))
module Interprocedural = AbstractInterpreter.Interprocedural (Summary) module Interprocedural = AbstractInterpreter.Interprocedural (Summary)
@ -810,12 +798,12 @@ let analyze_procedure callback =
~f:add_owned_formal ~f:add_owned_formal
owned_formals owned_formals
~init:ThreadSafetyDomain.empty.attribute_map in ~init:ThreadSafetyDomain.empty.attribute_map in
{ ThreadSafetyDomain.empty with attribute_map; threads; } { ThreadSafetyDomain.empty with attribute_map; threads; }, IdAccessPathMapDomain.empty
else else
{ ThreadSafetyDomain.empty with threads; } in { ThreadSafetyDomain.empty with threads; }, IdAccessPathMapDomain.empty in
match Analyzer.compute_post proc_data ~initial with match Analyzer.compute_post proc_data ~initial with
| Some { threads; locks; accesses; attribute_map; } -> | Some ({ threads; locks; accesses; attribute_map; }, _) ->
let return_var_ap = let return_var_ap =
AccessPath.of_pvar AccessPath.of_pvar
(Pvar.get_ret_pvar (Procdesc.get_proc_name pdesc)) (Pvar.get_ret_pvar (Procdesc.get_proc_name pdesc))

@ -201,20 +201,18 @@ type astate =
threads: ThreadsDomain.astate; threads: ThreadsDomain.astate;
locks : LocksDomain.astate; locks : LocksDomain.astate;
accesses : AccessDomain.astate; accesses : AccessDomain.astate;
id_map : IdAccessPathMapDomain.astate;
attribute_map : AttributeMapDomain.astate; attribute_map : AttributeMapDomain.astate;
} }
type summary = ThreadsDomain.astate * LocksDomain.astate type summary =
* AccessDomain.astate * AttributeSetDomain.astate ThreadsDomain.astate * LocksDomain.astate * AccessDomain.astate * AttributeSetDomain.astate
let empty = let empty =
let threads = false in let threads = false in
let locks = false in let locks = false in
let accesses = AccessDomain.empty in let accesses = AccessDomain.empty in
let id_map = IdAccessPathMapDomain.empty in
let attribute_map = AccessPath.RawMap.empty in let attribute_map = AccessPath.RawMap.empty in
{ threads; locks; accesses; id_map; attribute_map; } { threads; locks; accesses; attribute_map; }
let (<=) ~lhs ~rhs = let (<=) ~lhs ~rhs =
if phys_equal lhs rhs if phys_equal lhs rhs
@ -223,7 +221,6 @@ let (<=) ~lhs ~rhs =
ThreadsDomain.(<=) ~lhs:lhs.threads ~rhs:rhs.threads && ThreadsDomain.(<=) ~lhs:lhs.threads ~rhs:rhs.threads &&
LocksDomain.(<=) ~lhs:lhs.locks ~rhs:rhs.locks && LocksDomain.(<=) ~lhs:lhs.locks ~rhs:rhs.locks &&
AccessDomain.(<=) ~lhs:lhs.accesses ~rhs:rhs.accesses && AccessDomain.(<=) ~lhs:lhs.accesses ~rhs:rhs.accesses &&
IdAccessPathMapDomain.(<=) ~lhs:lhs.id_map ~rhs:rhs.id_map &&
AttributeMapDomain.(<=) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map AttributeMapDomain.(<=) ~lhs:lhs.attribute_map ~rhs:rhs.attribute_map
let join astate1 astate2 = let join astate1 astate2 =
@ -234,9 +231,8 @@ let join astate1 astate2 =
let threads = ThreadsDomain.join astate1.threads astate2.threads in let threads = ThreadsDomain.join astate1.threads astate2.threads in
let locks = LocksDomain.join astate1.locks astate2.locks in let locks = LocksDomain.join astate1.locks astate2.locks in
let accesses = AccessDomain.join astate1.accesses astate2.accesses in let accesses = AccessDomain.join astate1.accesses astate2.accesses in
let id_map = IdAccessPathMapDomain.join astate1.id_map astate2.id_map in
let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in let attribute_map = AttributeMapDomain.join astate1.attribute_map astate2.attribute_map in
{ threads; locks; accesses; id_map; attribute_map; } { threads; locks; accesses; attribute_map; }
let widen ~prev ~next ~num_iters = let widen ~prev ~next ~num_iters =
if phys_equal prev next if phys_equal prev next
@ -246,10 +242,9 @@ let widen ~prev ~next ~num_iters =
let threads = ThreadsDomain.widen ~prev:prev.threads ~next:next.threads ~num_iters in let threads = ThreadsDomain.widen ~prev:prev.threads ~next:next.threads ~num_iters in
let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in let locks = LocksDomain.widen ~prev:prev.locks ~next:next.locks ~num_iters in
let accesses = AccessDomain.widen ~prev:prev.accesses ~next:next.accesses ~num_iters in let accesses = AccessDomain.widen ~prev:prev.accesses ~next:next.accesses ~num_iters in
let id_map = IdAccessPathMapDomain.widen ~prev:prev.id_map ~next:next.id_map ~num_iters in
let attribute_map = let attribute_map =
AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters in AttributeMapDomain.widen ~prev:prev.attribute_map ~next:next.attribute_map ~num_iters in
{ threads; locks; accesses; id_map; attribute_map; } { threads; locks; accesses; attribute_map; }
let pp_summary fmt (threads, locks, accesses, return_attributes) = let pp_summary fmt (threads, locks, accesses, return_attributes) =
F.fprintf F.fprintf
@ -260,13 +255,11 @@ let pp_summary fmt (threads, locks, accesses, return_attributes) =
AccessDomain.pp accesses AccessDomain.pp accesses
AttributeSetDomain.pp return_attributes AttributeSetDomain.pp return_attributes
let pp fmt { threads; locks; accesses; id_map; attribute_map; } = let pp fmt { threads; locks; accesses; attribute_map; } =
F.fprintf F.fprintf
fmt fmt
"Threads: %a Locks: %a Accesses %a Id Map: %a Attribute Map:\ "Threads: %a Locks: %a Accesses: %a Attribute Map: %a"
%a"
ThreadsDomain.pp threads ThreadsDomain.pp threads
LocksDomain.pp locks LocksDomain.pp locks
AccessDomain.pp accesses AccessDomain.pp accesses
IdAccessPathMapDomain.pp id_map
AttributeMapDomain.pp attribute_map AttributeMapDomain.pp attribute_map

@ -125,16 +125,14 @@ type astate =
(** boolean that is true if a lock must currently be held *) (** boolean that is true if a lock must currently be held *)
accesses : AccessDomain.astate; accesses : AccessDomain.astate;
(** read and writes accesses performed without ownership permissions *) (** read and writes accesses performed without ownership permissions *)
id_map : IdAccessPathMapDomain.astate;
(** map used to decompile temporary variables into access paths *)
attribute_map : AttributeMapDomain.astate; attribute_map : AttributeMapDomain.astate;
(** map of access paths to attributes such as owned, functional, ... *) (** map of access paths to attributes such as owned, functional, ... *)
} }
(** same as astate, but without [id_map]/[owned] (since they are local) and with the addition of the (** same as astate, but without [id_map]/[owned] (since they are local) and with the addition of the
attributes associated with the return value *) attributes associated with the return value *)
type summary = ThreadsDomain.astate * LocksDomain.astate type summary =
* AccessDomain.astate * AttributeSetDomain.astate ThreadsDomain.astate * LocksDomain.astate * AccessDomain.astate * AttributeSetDomain.astate
include AbstractDomain.WithBottom with type astate := astate include AbstractDomain.WithBottom with type astate := astate

@ -9,22 +9,28 @@
open! IStd open! IStd
(** Transfer functions that push abstract states across instructions. A typical client should
implement the Make signature to allow the transfer functions to be used with any kind of CFG. *)
module type S = sig module type S = sig
module CFG : ProcCfg.S module CFG : ProcCfg.S
(** abstract domain whose state we propagate *)
module Domain : AbstractDomain.S module Domain : AbstractDomain.S
(** read-only extra state (results of previous analyses, globals, etc.) *)
type extras type extras
type instr
val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> instr -> Domain.astate
end
module type SIL = sig
include (S with type instr := Sil.instr)
end
module type HIL = sig
include (S with type instr := HilInstr.t)
end
(** {A} instr {A'}. [node] is the node of the current instruction *) module type MakeSIL = functor (C : ProcCfg.S) -> sig
val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Domain.astate include (SIL with module CFG = C)
end end
module type Make = functor (C : ProcCfg.S) -> sig module type MakeHIL = functor (C : ProcCfg.S) -> sig
include (S with module CFG = C) include (HIL with module CFG = C)
end end

@ -21,10 +21,25 @@ module type S = sig
(** read-only extra state (results of previous analyses, globals, etc.) *) (** read-only extra state (results of previous analyses, globals, etc.) *)
type extras type extras
(** type of the instructions the transfer functions operate on *)
type instr
(** {A} instr {A'}. [node] is the node of the current instruction *) (** {A} instr {A'}. [node] is the node of the current instruction *)
val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> Sil.instr -> Domain.astate val exec_instr : Domain.astate -> extras ProcData.t -> CFG.node -> instr -> Domain.astate
end
module type SIL = sig
include (S with type instr := Sil.instr)
end
module type HIL = sig
include (S with type instr := HilInstr.t)
end
module type MakeSIL = functor (C : ProcCfg.S) -> sig
include (SIL with module CFG = C)
end end
module type Make = functor (C : ProcCfg.S) -> sig module type MakeHIL = functor (C : ProcCfg.S) -> sig
include (S with module CFG = C) include (HIL with module CFG = C)
end end

@ -29,45 +29,7 @@ module Make (TaintSpecification : TaintSpec.S) = struct
summary.payload.quandary summary.payload.quandary
end) end)
module Domain = struct module Domain = TaintDomain
type astate =
{
access_tree : TaintDomain.astate; (* mapping of access paths to trace sets *)
id_map : IdMapDomain.astate; (* mapping of id's to access paths for normalization *)
}
let empty =
let access_tree = TaintDomain.empty in
let id_map = IdMapDomain.empty in
{ access_tree; id_map; }
let (<=) ~lhs ~rhs =
if phys_equal lhs rhs
then true
else
TaintDomain.(<=) ~lhs:lhs.access_tree ~rhs:rhs.access_tree &&
IdMapDomain.(<=) ~lhs:lhs.id_map ~rhs:rhs.id_map
let join astate1 astate2 =
if phys_equal astate1 astate2
then astate1
else
let access_tree = TaintDomain.join astate1.access_tree astate2.access_tree in
let id_map = IdMapDomain.join astate1.id_map astate2.id_map in
{ access_tree; id_map; }
let widen ~prev ~next ~num_iters =
if phys_equal prev next
then prev
else
let access_tree =
TaintDomain.widen ~prev:prev.access_tree ~next:next.access_tree ~num_iters in
let id_map = IdMapDomain.widen ~prev:prev.id_map ~next:next.id_map ~num_iters in
{ access_tree; id_map; }
let pp fmt { access_tree; id_map; } =
F.fprintf fmt "(%a, %a)" TaintDomain.pp access_tree IdMapDomain.pp id_map
end
let is_global (var, _) = match var with let is_global (var, _) = match var with
| Var.ProgramVar pvar -> Pvar.is_global pvar | Var.ProgramVar pvar -> Pvar.is_global pvar
@ -94,10 +56,6 @@ module Make (TaintSpecification : TaintSpec.S) = struct
type extras = FormalMap.t type extras = FormalMap.t
let resolve_id id_map id =
try Some (IdMapDomain.find id id_map)
with Not_found -> None
(* get the node associated with [access_path] in [access_tree] *) (* get the node associated with [access_path] in [access_tree] *)
let access_path_get_node access_path access_tree (proc_data : FormalMap.t ProcData.t) = let access_path_get_node access_path access_tree (proc_data : FormalMap.t ProcData.t) =
match TaintDomain.get_node access_path access_tree with match TaintDomain.get_node access_path access_tree with
@ -131,13 +89,6 @@ module Make (TaintSpecification : TaintSpec.S) = struct
else AccessPath.Exact raw_access_path in else AccessPath.Exact raw_access_path in
access_path_get_node access_path access_tree proc_data access_path_get_node access_path access_tree proc_data
(* get the node associated with [exp] in [access_tree] *)
let exp_get_node ?(abstracted=false) exp typ { Domain.access_tree; id_map; } proc_data =
let f_resolve_id = resolve_id id_map in
match AccessPath.of_lhs_exp exp typ ~f_resolve_id with
| Some raw_access_path -> exp_get_node_ ~abstracted raw_access_path access_tree proc_data
| None -> None
(* get the node associated with [exp] in [access_tree] *) (* get the node associated with [exp] in [access_tree] *)
let hil_exp_get_node ?(abstracted=false) (exp : HilExp.t) access_tree proc_data = let hil_exp_get_node ?(abstracted=false) (exp : HilExp.t) access_tree proc_data =
match exp with match exp with
@ -195,7 +146,7 @@ module Make (TaintSpecification : TaintSpec.S) = struct
List.iter ~f:report_error (TraceDomain.get_reportable_paths ~cur_site trace ~trace_of_pname) List.iter ~f:report_error (TraceDomain.get_reportable_paths ~cur_site trace ~trace_of_pname)
let add_sinks sinks actuals ({ Domain.access_tree; } as astate) proc_data callee_site = let add_sinks sinks actuals access_tree proc_data callee_site =
(* add [sink] to the trace associated with the [formal_index]th actual *) (* add [sink] to the trace associated with the [formal_index]th actual *)
let add_sink_to_actual access_tree_acc (sink_param : TraceDomain.Sink.parameter) = let add_sink_to_actual access_tree_acc (sink_param : TraceDomain.Sink.parameter) =
match List.nth_exn actuals sink_param.index with match List.nth_exn actuals sink_param.index with
@ -226,17 +177,15 @@ module Make (TaintSpecification : TaintSpec.S) = struct
end end
| _ -> | _ ->
access_tree_acc in access_tree_acc in
let access_tree' = List.fold ~f:add_sink_to_actual ~init:access_tree sinks in List.fold ~f:add_sink_to_actual ~init:access_tree sinks
{ astate with Domain.access_tree = access_tree'; }
let apply_summary let apply_summary
ret_opt ret_opt
(actuals : HilExp.t list) (actuals : HilExp.t list)
summary summary
(astate_in : Domain.astate) caller_access_tree
(proc_data : FormalMap.t ProcData.t) (proc_data : FormalMap.t ProcData.t)
callee_site = callee_site =
let caller_access_tree = astate_in.access_tree in
let get_caller_ap formal_ap = let get_caller_ap formal_ap =
let apply_return ret_ap = match ret_opt with let apply_return ret_ap = match ret_opt with
@ -309,173 +258,148 @@ module Make (TaintSpecification : TaintSpec.S) = struct
ignore (instantiate_and_report callee_trace TraceDomain.empty access_tree_acc); ignore (instantiate_and_report callee_trace TraceDomain.empty access_tree_acc);
access_tree_acc in access_tree_acc in
let access_tree = TaintDomain.trace_fold
TaintDomain.trace_fold add_to_caller_tree
add_to_caller_tree (TaintSpecification.of_summary_access_tree summary)
(TaintSpecification.of_summary_access_tree summary) caller_access_tree
caller_access_tree in
{ astate_in with access_tree; } let exec_instr
(astate : Domain.astate) (proc_data : FormalMap.t ProcData.t) _ (instr : HilInstr.t) =
let exec_hil_instr (astate : Domain.astate) (proc_data : FormalMap.t ProcData.t) instr = match instr with
let exec_instr_ (instr : HilInstr.t) = match instr with | Write (((Var.ProgramVar pvar, _), []), HilExp.Exception _, _) when Pvar.is_return pvar ->
| Write (((Var.ProgramVar pvar, _), []), HilExp.Exception _, _) when Pvar.is_return pvar -> (* the Java frontend translates `throw Exception` as `return Exception`, which is a bit
(* the Java frontend translates `throw Exception` as `return Exception`, which is a bit
wonky. this translation causes problems for us in computing a summary when an wonky. this translation causes problems for us in computing a summary when an
exception is "returned" from a void function. skip code like this for now, fix via exception is "returned" from a void function. skip code like this for now, fix via
t14159157 later *) t14159157 later *)
astate astate
| Write (((Var.ProgramVar pvar, _), []), rhs_exp, _) | Write (((Var.ProgramVar pvar, _), []), rhs_exp, _)
when Pvar.is_return pvar && HilExp.is_null_literal rhs_exp && when Pvar.is_return pvar && HilExp.is_null_literal rhs_exp &&
Typ.equal_desc Tvoid (Procdesc.get_ret_type proc_data.pdesc).desc -> Typ.equal_desc Tvoid (Procdesc.get_ret_type proc_data.pdesc).desc ->
(* similar to the case above; the Java frontend translates "return no exception" as (* similar to the case above; the Java frontend translates "return no exception" as
`return null` in a void function *) `return null` in a void function *)
astate astate
| Write (lhs_access_path, rhs_exp, _) -> | Write (lhs_access_path, rhs_exp, _) ->
let access_tree = let rhs_node =
let rhs_node = Option.value
Option.value (hil_exp_get_node rhs_exp astate proc_data)
(hil_exp_get_node rhs_exp astate.access_tree proc_data) ~default:TaintDomain.empty_node in
~default:TaintDomain.empty_node in TaintDomain.add_node (AccessPath.Exact lhs_access_path) rhs_node astate
TaintDomain.add_node (AccessPath.Exact lhs_access_path) rhs_node astate.access_tree in
{ astate with access_tree; } | Call (ret_opt, Direct called_pname, actuals, call_flags, callee_loc) ->
let handle_unknown_call callee_pname access_tree =
| Call (ret_opt, Direct called_pname, actuals, call_flags, callee_loc) -> let is_variadic = match callee_pname with
let handle_unknown_call callee_pname access_tree = | Typ.Procname.Java pname ->
let is_variadic = match callee_pname with begin
| Typ.Procname.Java pname -> match List.rev (Typ.Procname.java_get_parameters pname) with
begin | (_, "java.lang.Object[]") :: _ -> true
match List.rev (Typ.Procname.java_get_parameters pname) with | _ -> false
| (_, "java.lang.Object[]") :: _ -> true end
| _ -> false | _ -> false in
end let should_taint_typ typ = is_variadic || TaintSpecification.is_taintable_type typ in
| _ -> false in let exp_join_traces trace_acc exp =
let should_taint_typ typ = is_variadic || TaintSpecification.is_taintable_type typ in match hil_exp_get_node ~abstracted:true exp access_tree proc_data with
let exp_join_traces trace_acc exp = | Some (trace, _) -> TraceDomain.join trace trace_acc
match hil_exp_get_node ~abstracted:true exp access_tree proc_data with | None -> trace_acc in
| Some (trace, _) -> TraceDomain.join trace trace_acc let propagate_to_access_path access_path actuals access_tree =
| None -> trace_acc in let initial_trace =
let propagate_to_access_path access_path actuals access_tree = access_path_get_trace access_path access_tree proc_data in
let initial_trace = let trace_with_propagation =
access_path_get_trace access_path access_tree proc_data in List.fold ~f:exp_join_traces ~init:initial_trace actuals in
let trace_with_propagation = let filtered_sources =
List.fold ~f:exp_join_traces ~init:initial_trace actuals in TraceDomain.Sources.filter (fun source ->
let filtered_sources = match TraceDomain.Source.get_footprint_access_path source with
TraceDomain.Sources.filter (fun source -> | Some access_path ->
match TraceDomain.Source.get_footprint_access_path source with Option.exists
| Some access_path -> (AccessPath.Raw.get_typ (AccessPath.extract access_path) proc_data.tenv)
Option.exists ~f:should_taint_typ
(AccessPath.Raw.get_typ (AccessPath.extract access_path) proc_data.tenv) | None ->
~f:should_taint_typ true)
| None -> (TraceDomain.sources trace_with_propagation) in
true) if TraceDomain.Sources.is_empty filtered_sources
(TraceDomain.sources trace_with_propagation) in
if TraceDomain.Sources.is_empty filtered_sources
then
access_tree
else
let trace' = TraceDomain.update_sources trace_with_propagation filtered_sources in
TaintDomain.add_trace access_path trace' access_tree in
let handle_unknown_call_ astate_acc propagation =
match propagation, actuals, ret_opt with
| _, [], _ ->
astate_acc
| TaintSpec.Propagate_to_return, actuals, Some ret_ap ->
propagate_to_access_path (AccessPath.Exact (ret_ap, [])) actuals astate_acc
| TaintSpec.Propagate_to_receiver,
AccessPath receiver_ap :: (_ :: _ as other_actuals),
_ ->
propagate_to_access_path (AccessPath.Exact receiver_ap) other_actuals astate_acc
| _ ->
astate_acc in
let propagations =
TaintSpecification.handle_unknown_call
callee_pname
(Option.map ~f:snd ret_opt)
actuals
proc_data.tenv in
List.fold ~f:handle_unknown_call_ ~init:access_tree propagations in
let analyze_call astate_acc callee_pname =
let call_site = CallSite.make callee_pname callee_loc in
let sinks = TraceDomain.Sink.get call_site actuals proc_data.ProcData.tenv in
let astate_with_sink = match sinks with
| [] -> astate
| sinks -> add_sinks sinks actuals astate proc_data call_site in
let source = TraceDomain.Source.get call_site proc_data.tenv in
let astate_with_source =
match source, ret_opt with
| Some source, Some ret_exp ->
let access_tree = add_source source ret_exp astate_with_sink.access_tree in
{ astate_with_sink with access_tree; }
| Some _, None ->
L.err
"Warning: %a is marked as a source, but has no return value"
Typ.Procname.pp callee_pname;
astate_with_sink
| None, _ ->
astate_with_sink in
let astate_with_summary =
if sinks <> [] || Option.is_some source
then
(* don't use a summary for a procedure that is a direct source or sink *)
astate_with_source
else
match Summary.read_summary proc_data.pdesc callee_pname with
| Some summary ->
apply_summary ret_opt actuals summary astate_with_source proc_data call_site
| None ->
let access_tree =
handle_unknown_call callee_pname astate_with_source.access_tree in
{ astate with access_tree; } in
Domain.join astate_acc astate_with_summary in
(* highly polymorphic call sites stress reactive mode too much by using too much memory.
here, we choose an arbitrary call limit that allows us to finish the analysis in
practice. this is obviously unsound; will try to remove in the future. *)
let max_calls = 3 in
let targets =
if List.length call_flags.cf_targets <= max_calls
then then
called_pname :: call_flags.cf_targets access_tree
else else
begin let trace' = TraceDomain.update_sources trace_with_propagation filtered_sources in
L.out TaintDomain.add_trace access_path trace' access_tree in
"Skipping highly polymorphic call site for %a@." Typ.Procname.pp called_pname; let handle_unknown_call_ astate_acc propagation =
[called_pname] match propagation, actuals, ret_opt with
end in | _, [], _ ->
(* for each possible target of the call, apply the summary. join all results together *) astate_acc
List.fold ~f:analyze_call ~init:Domain.empty targets | TaintSpec.Propagate_to_return, actuals, Some ret_ap ->
| _ -> propagate_to_access_path (AccessPath.Exact (ret_ap, [])) actuals astate_acc
astate in | TaintSpec.Propagate_to_receiver,
AccessPath receiver_ap :: (_ :: _ as other_actuals),
let f_resolve_id id = _ ->
try Some (IdAccessPathMapDomain.find id astate.id_map) propagate_to_access_path (AccessPath.Exact receiver_ap) other_actuals astate_acc
with Not_found -> None in | _ ->
match HilInstr.of_sil ~f_resolve_id instr with astate_acc in
| Bind (id, access_path) ->
let id_map = IdAccessPathMapDomain.add id access_path astate.id_map in let propagations =
{ astate with id_map; } TaintSpecification.handle_unknown_call
| Unbind ids -> callee_pname
let id_map = (Option.map ~f:snd ret_opt)
List.fold actuals
~f:(fun acc id -> IdAccessPathMapDomain.remove id acc) ~init:astate.id_map ids in proc_data.tenv in
{ astate with id_map; } List.fold ~f:handle_unknown_call_ ~init:access_tree propagations in
| Instr hil_instr ->
exec_instr_ hil_instr let analyze_call astate_acc callee_pname =
| Ignore -> let call_site = CallSite.make callee_pname callee_loc in
let sinks = TraceDomain.Sink.get call_site actuals proc_data.ProcData.tenv in
let astate_with_sink = match sinks with
| [] -> astate
| sinks -> add_sinks sinks actuals astate proc_data call_site in
let source = TraceDomain.Source.get call_site proc_data.tenv in
let astate_with_source =
match source, ret_opt with
| Some source, Some ret_exp ->
add_source source ret_exp astate_with_sink
| Some _, None ->
L.err
"Warning: %a is marked as a source, but has no return value"
Typ.Procname.pp callee_pname;
astate_with_sink
| None, _ ->
astate_with_sink in
let astate_with_summary =
if sinks <> [] || Option.is_some source
then
(* don't use a summary for a procedure that is a direct source or sink *)
astate_with_source
else
match Summary.read_summary proc_data.pdesc callee_pname with
| Some summary ->
apply_summary ret_opt actuals summary astate_with_source proc_data call_site
| None ->
handle_unknown_call callee_pname astate_with_source in
Domain.join astate_acc astate_with_summary in
(* highly polymorphic call sites stress reactive mode too much by using too much memory.
here, we choose an arbitrary call limit that allows us to finish the analysis in
practice. this is obviously unsound; will try to remove in the future. *)
let max_calls = 3 in
let targets =
if List.length call_flags.cf_targets <= max_calls
then
called_pname :: call_flags.cf_targets
else
begin
L.out
"Skipping highly polymorphic call site for %a@." Typ.Procname.pp called_pname;
[called_pname]
end in
(* for each possible target of the call, apply the summary. join all results together *)
List.fold ~f:analyze_call ~init:Domain.empty targets
| _ ->
astate astate
let exec_instr (astate : Domain.astate) (proc_data : FormalMap.t ProcData.t) _ instr =
exec_hil_instr astate proc_data instr
end end
module Analyzer = AbstractInterpreter.Make (ProcCfg.Exceptional) (TransferFunctions) module Analyzer =
AbstractInterpreter.Make (ProcCfg.Exceptional) (LowerHil.Make(TransferFunctions))
let make_summary formal_map access_tree = let make_summary formal_map access_tree =
(* if a trace has footprint sources, attach them to the appropriate footprint var *) (* if a trace has footprint sources, attach them to the appropriate footprint var *)
@ -565,9 +489,7 @@ module Make (TaintSpecification : TaintSpec.S) = struct
acc) acc)
~init:TaintDomain.empty ~init:TaintDomain.empty
(TraceDomain.Source.get_tainted_formals pdesc tenv) in (TraceDomain.Source.get_tainted_formals pdesc tenv) in
if TaintDomain.BaseMap.is_empty access_tree access_tree, IdAccessPathMapDomain.empty in
then Domain.empty
else { Domain.empty with Domain.access_tree; } in
let compute_post (proc_data : FormalMap.t ProcData.t) = let compute_post (proc_data : FormalMap.t ProcData.t) =
if not (Procdesc.did_preanalysis proc_data.pdesc) if not (Procdesc.did_preanalysis proc_data.pdesc)
@ -578,7 +500,7 @@ module Make (TaintSpecification : TaintSpec.S) = struct
end; end;
let initial = make_initial proc_data.pdesc in let initial = make_initial proc_data.pdesc in
match Analyzer.compute_post proc_data ~initial with match Analyzer.compute_post proc_data ~initial with
| Some { access_tree; } -> | Some (access_tree, _) ->
Some (make_summary proc_data.extras access_tree) Some (make_summary proc_data.extras access_tree)
| None -> | None ->
if Procdesc.Node.get_succs (Procdesc.get_start_node proc_data.pdesc) <> [] if Procdesc.Node.get_succs (Procdesc.get_start_node proc_data.pdesc) <> []

@ -49,7 +49,8 @@ module MockTaintAnalysis = TaintAnalysis.Make(struct
let is_taintable_type _ = true let is_taintable_type _ = true
end) end)
module TestInterpreter = AnalyzerTester.Make (ProcCfg.Normal) (MockTaintAnalysis.TransferFunctions) module TestInterpreter =
AnalyzerTester.Make (ProcCfg.Normal) (LowerHil.Make (MockTaintAnalysis.TransferFunctions))
let tests = let tests =
let open OUnit2 in let open OUnit2 in
@ -89,7 +90,7 @@ let tests =
if not (MockTrace.is_empty t) if not (MockTrace.is_empty t)
then (ap, t) :: acc then (ap, t) :: acc
else acc) else acc)
astate.MockTaintAnalysis.Domain.access_tree (fst astate)
[] in [] in
PrettyPrintable.pp_collection ~pp_item fmt (List.rev trace_assocs) in PrettyPrintable.pp_collection ~pp_item fmt (List.rev trace_assocs) in
let assign_to_source ret_str = let assign_to_source ret_str =
@ -225,5 +226,5 @@ let tests =
] |> TestInterpreter.create_tests ] |> TestInterpreter.create_tests
~pp_opt:pp_sparse ~pp_opt:pp_sparse
FormalMap.empty FormalMap.empty
~initial:MockTaintAnalysis.Domain.empty in ~initial:(MockTaintAnalysis.Domain.empty, IdAccessPathMapDomain.empty) in
"taint_test_suite">:::test_list "taint_test_suite">:::test_list

@ -158,7 +158,8 @@ module StructuredSil = struct
call_unknown None arg_strs call_unknown None arg_strs
end end
module Make (CFG : ProcCfg.S with type node = Procdesc.Node.t) (T : TransferFunctions.Make) = struct module Make
(CFG : ProcCfg.S with type node = Procdesc.Node.t) (T : TransferFunctions.MakeSIL) = struct
open StructuredSil open StructuredSil

Loading…
Cancel
Save