[pulse] change ISL functions to return list of results

Summary:
It makes more sense to return a list of results than a result of lists:
the latter stops the execution on *all* the disjuncts that would have
been in the list as soon as *one* of them fails. This is the same issue
we solved for non-ISL pulse models earlier.

Reviewed By: skcho

Differential Revision: D26818409

fbshipit-source-id: 7cc1d8b39
master
Jules Villard 4 years ago committed by Facebook GitHub Bot
parent 8db09e8e0a
commit c7b0cc3c59

@ -243,7 +243,7 @@ module PulseTransferFunctions = struct
[astate] [astate]
| ContinueProgram astate -> | ContinueProgram astate ->
dispatch_call analysis_data ret call_exp actuals location call_flags astate dispatch_call analysis_data ret call_exp actuals location call_flags astate
|> PulseReport.report_results analysis_data ) |> PulseReport.report_exec_results analysis_data )
in in
let dynamic_types_unreachable = let dynamic_types_unreachable =
PulseOperations.get_dynamic_type_unreachable_values vars astate PulseOperations.get_dynamic_type_unreachable_values vars astate
@ -279,30 +279,28 @@ module PulseTransferFunctions = struct
| Load {id= lhs_id; e= rhs_exp; loc} -> | Load {id= lhs_id; e= rhs_exp; loc} ->
(* [lhs_id := *rhs_exp] *) (* [lhs_id := *rhs_exp] *)
let result = let result =
let+ astate_rhs_addr_hists = if Config.pulse_isl then
if Config.pulse_isl then PulseOperations.eval_deref_isl loc rhs_exp astate
let+ astate_rhs_addr_hists = PulseOperations.eval_deref_isl loc rhs_exp astate in |> List.map ~f:(fun result ->
astate_rhs_addr_hists let+ astate, rhs_addr_hist = result in
else PulseOperations.write_id lhs_id rhs_addr_hist astate )
let+ astate_rhs_addr_hist = PulseOperations.eval_deref loc rhs_exp astate in else
[astate_rhs_addr_hist] [ (let+ astate, rhs_addr_hist = PulseOperations.eval_deref loc rhs_exp astate in
in PulseOperations.write_id lhs_id rhs_addr_hist astate) ]
List.map astate_rhs_addr_hists ~f:(fun (astate, rhs_addr_hist) ->
PulseOperations.write_id lhs_id rhs_addr_hist astate )
in in
PulseReport.report_list_result analysis_data result PulseReport.report_results analysis_data result
| Store {e1= lhs_exp; e2= rhs_exp; loc} -> | Store {e1= lhs_exp; e2= rhs_exp; loc} ->
(* [*lhs_exp := rhs_exp] *) (* [*lhs_exp := rhs_exp] *)
let event = ValueHistory.Assignment loc in let event = ValueHistory.Assignment loc in
let result = let result =
let* astate, (rhs_addr, rhs_history) = let<*> astate, (rhs_addr, rhs_history) =
PulseOperations.eval NoAccess loc rhs_exp astate PulseOperations.eval NoAccess loc rhs_exp astate
in in
let* is_structured, ls_astate_lhs_addr_hist = let<*> is_structured, ls_astate_lhs_addr_hist =
if Config.pulse_isl then PulseOperations.eval_structure_isl Write loc lhs_exp astate if Config.pulse_isl then PulseOperations.eval_structure_isl Write loc lhs_exp astate
else else
let* astate, lhs_addr_hist = PulseOperations.eval Write loc lhs_exp astate in let+ astate, lhs_addr_hist = PulseOperations.eval Write loc lhs_exp astate in
Ok (false, [(astate, lhs_addr_hist)]) (false, [Ok (astate, lhs_addr_hist)])
in in
let write_function lhs_addr_hist astate = let write_function lhs_addr_hist astate =
if is_structured then if is_structured then
@ -310,40 +308,35 @@ module PulseTransferFunctions = struct
~obj:(rhs_addr, event :: rhs_history) ~obj:(rhs_addr, event :: rhs_history)
astate astate
else else
let+ astate = [ PulseOperations.write_deref loc ~ref:lhs_addr_hist
PulseOperations.write_deref loc ~ref:lhs_addr_hist
~obj:(rhs_addr, event :: rhs_history) ~obj:(rhs_addr, event :: rhs_history)
astate astate ]
in
[astate]
in in
let* astates = let astates =
List.fold_result ls_astate_lhs_addr_hist ~init:[] List.concat_map ls_astate_lhs_addr_hist ~f:(fun result ->
~f:(fun acc_astates (astate, lhs_addr_hist) -> let<*> astate, lhs_addr_hist = result in
match (Config.pulse_isl, astate.AbductiveDomain.isl_status) with match (Config.pulse_isl, astate.AbductiveDomain.isl_status) with
| false, _ | true, ISLOk -> | false, _ | true, ISLOk ->
let+ astates = write_function lhs_addr_hist astate in write_function lhs_addr_hist astate
List.rev_append astates acc_astates
| true, ISLError -> | true, ISLError ->
Ok (astate :: acc_astates) ) [Ok astate] )
in in
let astates = let astates =
if Topl.is_deep_active () then if Topl.is_deep_active () then
List.map astates ~f:(fun astate -> List.map astates ~f:(fun result ->
let+ astate = result in
topl_store_step loc ~lhs:lhs_exp ~rhs:rhs_exp astate ) topl_store_step loc ~lhs:lhs_exp ~rhs:rhs_exp astate )
else astates else astates
in in
match lhs_exp with match lhs_exp with
| Lvar pvar when Pvar.is_return pvar -> | Lvar pvar when Pvar.is_return pvar ->
List.fold_result astates ~init:[] ~f:(fun acc astate -> List.map astates ~f:(fun result ->
let+ astate = let* astate = result in
PulseOperations.check_address_escape loc proc_desc rhs_addr rhs_history astate PulseOperations.check_address_escape loc proc_desc rhs_addr rhs_history astate )
in
astate :: acc )
| _ -> | _ ->
Ok astates astates
in in
PulseReport.report_list_result analysis_data result PulseReport.report_results analysis_data result
| Prune (condition, loc, _is_then_branch, _if_kind) -> | Prune (condition, loc, _is_then_branch, _if_kind) ->
(let<*> astate = PulseOperations.prune loc ~condition astate in (let<*> astate = PulseOperations.prune loc ~condition astate in
if PulseArithmetic.is_unsat_cheap astate then if PulseArithmetic.is_unsat_cheap astate then
@ -352,10 +345,10 @@ module PulseTransferFunctions = struct
else else
(* [condition] is true or unknown value: go into the branch *) (* [condition] is true or unknown value: go into the branch *)
[Ok (ContinueProgram astate)]) [Ok (ContinueProgram astate)])
|> PulseReport.report_results analysis_data |> PulseReport.report_exec_results analysis_data
| Call (ret, call_exp, actuals, loc, call_flags) -> | Call (ret, call_exp, actuals, loc, call_flags) ->
dispatch_call analysis_data ret call_exp actuals loc call_flags astate dispatch_call analysis_data ret call_exp actuals loc call_flags astate
|> PulseReport.report_results analysis_data |> PulseReport.report_exec_results analysis_data
| Metadata (ExitScope (vars, location)) -> | Metadata (ExitScope (vars, location)) ->
let remove_vars vars astates = let remove_vars vars astates =
List.concat_map astates ~f:(fun (astate : Domain.t) -> List.concat_map astates ~f:(fun (astate : Domain.t) ->
@ -363,12 +356,8 @@ module PulseTransferFunctions = struct
| ISLLatentMemoryError _ | AbortProgram _ | ExitProgram _ | LatentAbortProgram _ -> | ISLLatentMemoryError _ | AbortProgram _ | ExitProgram _ | LatentAbortProgram _ ->
[astate] [astate]
| ContinueProgram astate -> | ContinueProgram astate ->
( match PulseOperations.remove_vars tenv vars location astate with PulseOperations.remove_vars tenv vars location astate
| Ok astate -> |> PulseReport.report_result analysis_data )
Ok [astate]
| Error _ as error ->
error )
|> PulseReport.report_list_result analysis_data )
in in
if Procname.is_java (Procdesc.get_proc_name proc_desc) then if Procname.is_java (Procdesc.get_proc_name proc_desc) then
remove_vars vars [ContinueProgram astate] remove_vars vars [ContinueProgram astate]

@ -123,11 +123,10 @@ module Misc = struct
let astates_alloc = let astates_alloc =
let astate = PulseArithmetic.and_positive (fst deleted_access) astate in let astate = PulseArithmetic.and_positive (fst deleted_access) astate in
if Config.pulse_isl then if Config.pulse_isl then
match PulseOperations.invalidate_biad_isl location invalidation deleted_access astate with PulseOperations.invalidate_biad_isl location invalidation deleted_access astate
| Error _ as err -> |> List.map ~f:(fun result ->
[err] let+ astate = result in
| Ok astates -> ContinueProgram astate )
List.map astates ~f:(fun astate -> Ok (ContinueProgram astate))
else else
let<+> astate = PulseOperations.invalidate location invalidation deleted_access astate in let<+> astate = PulseOperations.invalidate location invalidation deleted_access astate in
astate astate

@ -22,10 +22,7 @@ let mk_objc_method_nil_summary_aux proc_desc astate =
let astate = PulseArithmetic.prune_eq_zero (fst self_value) astate in let astate = PulseArithmetic.prune_eq_zero (fst self_value) astate in
let ret_var = Procdesc.get_ret_var proc_desc in let ret_var = Procdesc.get_ret_var proc_desc in
let* astate, ret_var_addr_hist = PulseOperations.eval Write location (Lvar ret_var) astate in let* astate, ret_var_addr_hist = PulseOperations.eval Write location (Lvar ret_var) astate in
let+ astate = PulseOperations.write_deref location ~ref:ret_var_addr_hist ~obj:self_value astate
PulseOperations.write_deref location ~ref:ret_var_addr_hist ~obj:self_value astate
in
[astate]
let mk_objc_method_nil_summary ({InterproceduralAnalysis.proc_desc} as analysis_data) initial = let mk_objc_method_nil_summary ({InterproceduralAnalysis.proc_desc} as analysis_data) initial =
@ -39,7 +36,7 @@ let mk_objc_method_nil_summary ({InterproceduralAnalysis.proc_desc} as analysis_
However, there is an exception in the case where the return type is non-POD. However, there is an exception in the case where the return type is non-POD.
In that case it's UB and we want to report an error. *) In that case it's UB and we want to report an error. *)
let result = mk_objc_method_nil_summary_aux proc_desc astate in let result = mk_objc_method_nil_summary_aux proc_desc astate in
Some (PulseReport.report_list_result analysis_data result) Some (PulseReport.report_result analysis_data result)
| ContinueProgram _, _ | ContinueProgram _, _
| ExitProgram _, _ | ExitProgram _, _
| AbortProgram _, _ | AbortProgram _, _
@ -55,10 +52,9 @@ let append_objc_self_positive ({InterproceduralAnalysis.proc_desc} as analysis_d
| ContinueProgram astate -> | ContinueProgram astate ->
let result = let result =
let+ astate, value = PulseOperations.eval_deref location (Lvar self) astate in let+ astate, value = PulseOperations.eval_deref location (Lvar self) astate in
let astate = PulseArithmetic.prune_positive (fst value) astate in PulseArithmetic.prune_positive (fst value) astate
[astate]
in in
PulseReport.report_list_result analysis_data result PulseReport.report_result analysis_data result
| ExitProgram _ | AbortProgram _ | LatentAbortProgram _ | ISLLatentMemoryError _ -> | ExitProgram _ | AbortProgram _ | LatentAbortProgram _ | ISLLatentMemoryError _ ->
[astate] [astate]

@ -57,33 +57,29 @@ let check_addr_access access_mode location (address, history) astate =
let check_and_abduce_addr_access_isl access_mode location (address, history) ?(null_noop = false) let check_and_abduce_addr_access_isl access_mode location (address, history) ?(null_noop = false)
astate = astate =
let access_trace = Trace.Immediate {location; history} in let access_trace = Trace.Immediate {location; history} in
let* astates = match AddressAttributes.check_valid_isl access_trace address ~null_noop astate with
AddressAttributes.check_valid_isl access_trace address ~null_noop astate | Error (invalidation, invalidation_trace, astate) ->
|> Result.map_error ~f:(fun (invalidation, invalidation_trace, astate) -> [ Error
( Diagnostic.AccessToInvalidAddress ( Diagnostic.AccessToInvalidAddress
{calling_context= []; invalidation; invalidation_trace; access_trace} {calling_context= []; invalidation; invalidation_trace; access_trace}
, astate ) ) , astate ) ]
in | Ok astates -> (
match access_mode with match access_mode with
| Read -> | Read ->
List.fold_result astates ~init:[] ~f:(fun astates astate -> List.map astates ~f:(fun astate ->
match AddressAttributes.check_initialized access_trace address astate with AddressAttributes.check_initialized access_trace address astate
| Error _ -> |> Result.map_error ~f:(fun () ->
Error ( Diagnostic.ReadUninitializedValue {calling_context= []; trace= access_trace}
( Diagnostic.ReadUninitializedValue {calling_context= []; trace= access_trace} , AbductiveDomain.set_isl_status ISLError astate ) ) )
, AbductiveDomain.set_isl_status ISLError astate ) | Write ->
| Ok ok_astate -> List.map astates ~f:(fun astate ->
Ok (ok_astate :: astates) ) match astate.AbductiveDomain.isl_status with
| Write -> | ISLOk ->
Ok Ok (AbductiveDomain.initialize address astate)
(List.map astates ~f:(fun astate -> | ISLError ->
match astate.AbductiveDomain.isl_status with Ok astate )
| ISLOk -> | NoAccess ->
AbductiveDomain.initialize address astate List.map ~f:(fun astate -> Ok astate) astates )
| ISLError ->
astate ))
| NoAccess ->
Ok astates
module Closures = struct module Closures = struct
@ -166,18 +162,17 @@ let eval_access mode location addr_hist access astate =
let eval_access_biad_isl mode location addr_hist access astate = let eval_access_biad_isl mode location addr_hist access astate =
let map_ok addr_hist access astates = let map_ok addr_hist access results =
List.map List.map results ~f:(fun result ->
~f:(fun astate -> let+ astate = result in
match astate.AbductiveDomain.isl_status with match astate.AbductiveDomain.isl_status with
| ISLOk -> | ISLOk ->
Memory.eval_edge addr_hist access astate Memory.eval_edge addr_hist access astate
| ISLError -> | ISLError ->
(astate, addr_hist) ) (astate, addr_hist) )
astates
in in
let+ astates = check_and_abduce_addr_access_isl mode location addr_hist astate in let results = check_and_abduce_addr_access_isl mode location addr_hist astate in
map_ok addr_hist access astates map_ok addr_hist access results
let eval mode location exp0 astate = let eval mode location exp0 astate =
@ -267,52 +262,47 @@ let eval_deref location exp astate =
let eval_structure_isl mode loc exp astate = let eval_structure_isl mode loc exp astate =
match (exp : Exp.t) with match (exp : Exp.t) with
| Lfield (exp', field, _) -> | Lfield (exp', field, _) ->
let* astate, addr_hist = eval mode loc exp' astate in let+ astate, addr_hist = eval mode loc exp' astate in
let+ astates = let astates = eval_access_biad_isl mode loc addr_hist (FieldAccess field) astate in
eval_access_biad_isl mode loc addr_hist (HilExp.Access.FieldAccess field) astate
in
(false, astates) (false, astates)
| Lindex (exp', exp_index) -> | Lindex (exp', exp_index) ->
let* astate, addr_hist_index = eval mode loc exp_index astate in let* astate, addr_hist_index = eval mode loc exp_index astate in
let* astate, addr_hist = eval mode loc exp' astate in let+ astate, addr_hist = eval mode loc exp' astate in
let+ astates = let astates =
eval_access_biad_isl mode loc addr_hist eval_access_biad_isl mode loc addr_hist
(HilExp.Access.ArrayAccess (StdTyp.void, fst addr_hist_index)) (ArrayAccess (StdTyp.void, fst addr_hist_index))
astate astate
in in
(false, astates) (false, astates)
| _ -> | _ ->
let+ astate, (addr, history) = eval mode loc exp astate in let+ astate, (addr, history) = eval mode loc exp astate in
(true, [(astate, (addr, history))]) (true, [Ok (astate, (addr, history))])
let eval_deref_biad_isl location access addr_hist astate = let eval_deref_biad_isl location access addr_hist astate =
let+ astates = check_and_abduce_addr_access_isl Read location addr_hist astate in let astates = check_and_abduce_addr_access_isl Read location addr_hist astate in
List.map List.map astates ~f:(fun astate ->
~f:(fun astate -> let+ astate = astate in
match astate.AbductiveDomain.isl_status with match astate.AbductiveDomain.isl_status with
| ISLOk -> | ISLOk ->
Memory.eval_edge addr_hist access astate Memory.eval_edge addr_hist access astate
| ISLError -> | ISLError ->
(astate, addr_hist) ) (astate, addr_hist) )
astates
let eval_deref_isl location exp astate = let eval_deref_isl location exp astate =
let* is_structured, ls_astate_addr_hist = eval_structure_isl Read location exp astate in let<*> is_structured, ls_astate_addr_hist = eval_structure_isl Read location exp astate in
let eval_deref_function (astate, addr_hist) = let eval_deref_function (astate, addr_hist) =
if is_structured then eval_deref_biad_isl location Dereference addr_hist astate if is_structured then eval_deref_biad_isl location Dereference addr_hist astate
else else [eval_deref location exp astate]
let+ astate = eval_deref location exp astate in
[astate]
in in
List.fold_result ls_astate_addr_hist ~init:[] ~f:(fun acc_astates ((astate, _) as astate_addr) -> List.concat_map ls_astate_addr_hist ~f:(fun result ->
let<*> ((astate, _) as astate_addr) = result in
match astate.AbductiveDomain.isl_status with match astate.AbductiveDomain.isl_status with
| ISLOk -> | ISLOk ->
let+ astates = eval_deref_function astate_addr in eval_deref_function astate_addr
acc_astates @ astates
| ISLError -> | ISLError ->
Ok (acc_astates @ [astate_addr]) ) [Ok astate_addr] )
let realloc_pvar tenv pvar typ location astate = let realloc_pvar tenv pvar typ location astate =
@ -338,16 +328,14 @@ let write_access location addr_trace_ref access addr_trace_obj astate =
let write_access_biad_isl location addr_trace_ref access addr_trace_obj astate = let write_access_biad_isl location addr_trace_ref access addr_trace_obj astate =
let* astates = check_and_abduce_addr_access_isl Write location addr_trace_ref astate in check_and_abduce_addr_access_isl Write location addr_trace_ref astate
List.fold_result astates ~init:[] ~f:(fun acc ast -> |> List.map ~f:(fun result ->
let astate = let+ astate = result in
match ast.AbductiveDomain.isl_status with match astate.AbductiveDomain.isl_status with
| ISLOk -> | ISLOk ->
Memory.add_edge addr_trace_ref access addr_trace_obj location ast Memory.add_edge addr_trace_ref access addr_trace_obj location astate
| ISLError -> | ISLError ->
ast astate )
in
Ok (astate :: acc) )
let write_deref location ~ref:addr_trace_ref ~obj:addr_trace_obj astate = let write_deref location ~ref:addr_trace_ref ~obj:addr_trace_obj astate =
@ -385,17 +373,14 @@ let invalidate location cause addr_trace astate =
let invalidate_biad_isl location cause (address, history) astate = let invalidate_biad_isl location cause (address, history) astate =
let+ astates = check_and_abduce_addr_access_isl NoAccess location (address, history) ~null_noop:true astate
check_and_abduce_addr_access_isl NoAccess location (address, history) ~null_noop:true astate |> List.map ~f:(fun result ->
in let+ astate = result in
List.map match astate.AbductiveDomain.isl_status with
~f:(fun astate -> | ISLOk ->
match astate.AbductiveDomain.isl_status with AddressAttributes.invalidate (address, history) cause location astate
| ISLOk -> | ISLError ->
AddressAttributes.invalidate (address, history) cause location astate astate )
| ISLError ->
astate )
astates
let invalidate_access location cause ref_addr_hist access astate = let invalidate_access location cause ref_addr_hist access astate =

@ -70,7 +70,7 @@ val eval_structure_isl :
-> Location.t -> Location.t
-> Exp.t -> Exp.t
-> t -> t
-> (bool * (t * (AbstractValue.t * ValueHistory.t)) list) access_result -> (bool * (t * (AbstractValue.t * ValueHistory.t)) access_result list) access_result
(** Similar to eval but apply to data structures and ISL abduction. Return a list of abduced states (** Similar to eval but apply to data structures and ISL abduction. Return a list of abduced states
(ISLOk and ISLErs); The boolean indicates whether it is data structures or not. *) (ISLOk and ISLErs); The boolean indicates whether it is data structures or not. *)
@ -80,7 +80,7 @@ val eval_deref : Location.t -> Exp.t -> t -> (t * (AbstractValue.t * ValueHistor
(** Like [eval] but evaluates [*exp]. *) (** Like [eval] but evaluates [*exp]. *)
val eval_deref_isl : val eval_deref_isl :
Location.t -> Exp.t -> t -> (t * (AbstractValue.t * ValueHistory.t)) list access_result Location.t -> Exp.t -> t -> (t * (AbstractValue.t * ValueHistory.t)) access_result list
val eval_access : val eval_access :
access_mode access_mode
@ -138,14 +138,14 @@ val write_deref_biad_isl :
-> AbstractValue.t HilExp.Access.t -> AbstractValue.t HilExp.Access.t
-> obj:AbstractValue.t * ValueHistory.t -> obj:AbstractValue.t * ValueHistory.t
-> t -> t
-> t list access_result -> t access_result list
val invalidate : val invalidate :
Location.t -> Invalidation.t -> AbstractValue.t * ValueHistory.t -> t -> t access_result Location.t -> Invalidation.t -> AbstractValue.t * ValueHistory.t -> t -> t access_result
(** record that the address is invalid *) (** record that the address is invalid *)
val invalidate_biad_isl : val invalidate_biad_isl :
Location.t -> Invalidation.t -> AbstractValue.t * ValueHistory.t -> t -> t list access_result Location.t -> Invalidation.t -> AbstractValue.t * ValueHistory.t -> t -> t access_result list
(** record that the address is invalid. If the address has not been allocated, abduce ISL specs for (** record that the address is invalid. If the address has not been allocated, abduce ISL specs for
both invalid (null, free, unint) and allocated heap. *) both invalid (null, free, unint) and allocated heap. *)

@ -57,22 +57,6 @@ let report_error tenv proc_desc err_log access_result =
ExecutionDomain.LatentAbortProgram {astate= astate_summary; latent_issue} ) ExecutionDomain.LatentAbortProgram {astate= astate_summary; latent_issue} )
let exec_list_of_list_result = function
| Ok posts ->
posts
| Error Unsat ->
[]
| Error (Sat post) ->
[post]
let report_list_result {tenv; InterproceduralAnalysis.proc_desc; err_log} result =
let open Result.Monad_infix in
report_error tenv proc_desc err_log result
>>| List.map ~f:(fun post -> ExecutionDomain.ContinueProgram post)
|> exec_list_of_list_result
let post_of_report_result = function let post_of_report_result = function
| Ok post -> | Ok post ->
Some post Some post
@ -82,6 +66,17 @@ let post_of_report_result = function
Some post Some post
let report_results {tenv; InterproceduralAnalysis.proc_desc; err_log} results = let report_exec_results {InterproceduralAnalysis.proc_desc; tenv; err_log} results =
List.filter_map results ~f:(fun exec_result -> List.filter_map results ~f:(fun exec_result ->
report_error tenv proc_desc err_log exec_result |> post_of_report_result ) report_error tenv proc_desc err_log exec_result |> post_of_report_result )
let report_results analysis_data results =
List.map results ~f:(fun result ->
let open IResult.Let_syntax in
let+ astate = result in
ExecutionDomain.ContinueProgram astate )
|> report_exec_results analysis_data
let report_result analysis_data result = report_results analysis_data [result]

@ -11,12 +11,17 @@ open PulseDomainInterface
type 'a access_result = ('a, Diagnostic.t * AbductiveDomain.t) result type 'a access_result = ('a, Diagnostic.t * AbductiveDomain.t) result
val report_list_result : val report_result :
PulseSummary.t InterproceduralAnalysis.t PulseSummary.t InterproceduralAnalysis.t
-> AbductiveDomain.t list access_result -> AbductiveDomain.t access_result
-> ExecutionDomain.t list -> ExecutionDomain.t list
val report_results : val report_results :
PulseSummary.t InterproceduralAnalysis.t PulseSummary.t InterproceduralAnalysis.t
-> AbductiveDomain.t access_result list
-> ExecutionDomain.t list
val report_exec_results :
PulseSummary.t InterproceduralAnalysis.t
-> ExecutionDomain.t access_result list -> ExecutionDomain.t access_result list
-> ExecutionDomain.t list -> ExecutionDomain.t list

Loading…
Cancel
Save