[HIL] Access expression

Summary: Preparing to extend HIL with Dereference and AddressOf expressions. Next steps: (1) change SIL -> HIL translation to preserve address of and dereference; (2) adapt analyses based on HIL to make use access expressions.

Reviewed By: jeremydubreil

Differential Revision: D6961928

fbshipit-source-id: 51da919
master
Daiva Naudziuniene 7 years ago committed by Facebook Github Bot
parent 2288e66063
commit 896e849bfc

@ -0,0 +1,39 @@
(*
* Copyright (c) 2018 - 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
type t =
| Base of AccessPath.base
| Offset of t * AccessPath.access
(* field/array access *)
| AddressOf of t
(* address of operator & *)
| Dereference of t
(* dereference operator * *)
[@@deriving compare]
(** convert to an AccessPath.t, ignoring AddressOf and Dereference for now *)
let rec to_access_path t =
match t with
| Base base ->
(base, [])
| Offset (ae, acc) ->
AccessPath.append (to_access_path ae) [acc]
| AddressOf ae ->
to_access_path ae
| Dereference ae ->
to_access_path ae
let of_access_path (base, accesses) =
let rec add_access accesses ae =
match accesses with [] -> ae | access :: rest -> add_access rest (Offset (ae, access))
in
add_access accesses (Base base)

@ -0,0 +1,24 @@
(*
* Copyright (c) 2018 - 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
type t =
| Base of AccessPath.base
| Offset of t * AccessPath.access
(* field/array access *)
| AddressOf of t
(* & *)
| Dereference of t
(* * *)
[@@deriving compare]
val to_access_path : t -> AccessPath.t
val of_access_path : AccessPath.t -> t

@ -12,7 +12,7 @@ module F = Format
module L = Logging module L = Logging
type t = type t =
| AccessPath of AccessPath.t | AccessExpression of AccessExpression.t
| UnaryOperator of Unop.t * t * Typ.t option | UnaryOperator of Unop.t * t * Typ.t option
| BinaryOperator of Binop.t * t * t | BinaryOperator of Binop.t * t * t
| Exception of t | Exception of t
@ -23,8 +23,8 @@ type t =
[@@deriving compare] [@@deriving compare]
let rec pp fmt = function let rec pp fmt = function
| AccessPath access_path -> | AccessExpression access_expr ->
AccessPath.pp fmt access_path AccessPath.pp fmt (AccessExpression.to_access_path access_expr)
| UnaryOperator (op, e, _) -> | UnaryOperator (op, e, _) ->
F.fprintf fmt "%s%a" (Unop.str op) pp e F.fprintf fmt "%s%a" (Unop.str op) pp e
| BinaryOperator (op, e1, e2) -> | BinaryOperator (op, e1, e2) ->
@ -43,8 +43,8 @@ let rec pp fmt = function
let rec get_typ tenv = function let rec get_typ tenv = function
| AccessPath access_path -> | AccessExpression access_expr ->
AccessPath.get_typ access_path tenv AccessPath.get_typ (AccessExpression.to_access_path access_expr) tenv
| UnaryOperator (_, _, typ_opt) -> | UnaryOperator (_, _, typ_opt) ->
typ_opt typ_opt
| BinaryOperator ((Lt | Gt | Le | Ge | Eq | Ne | LAnd | LOr), _, _) -> | BinaryOperator ((Lt | Gt | Le | Ge | Eq | Ne | LAnd | LOr), _, _) ->
@ -86,8 +86,8 @@ let rec get_typ tenv = function
let get_access_paths exp0 = let get_access_paths exp0 =
let rec get_access_paths_ exp acc = let rec get_access_paths_ exp acc =
match exp with match exp with
| AccessPath ap -> | AccessExpression ae ->
ap :: acc AccessExpression.to_access_path ae :: acc
| Cast (_, e) | UnaryOperator (_, e, _) | Exception e | Sizeof (_, Some e) -> | Cast (_, e) | UnaryOperator (_, e, _) | Exception e | Sizeof (_, Some e) ->
get_access_paths_ e acc get_access_paths_ e acc
| BinaryOperator (_, e1, e2) -> | BinaryOperator (_, e1, e2) ->
@ -113,7 +113,7 @@ let of_sil ~include_array_indexes ~f_resolve_id exp typ =
| None -> | None ->
AccessPath.of_id id typ AccessPath.of_id id typ
in in
AccessPath ap AccessExpression (AccessExpression.of_access_path ap)
| UnOp (op, e, typ_opt) -> | UnOp (op, e, typ_opt) ->
UnaryOperator (op, of_sil_ e typ, typ_opt) UnaryOperator (op, of_sil_ e typ, typ_opt)
| BinOp (op, e0, e1) -> | BinOp (op, e0, e1) ->
@ -136,7 +136,7 @@ let of_sil ~include_array_indexes ~f_resolve_id exp typ =
| Lfield (root_exp, fld, root_exp_typ) -> ( | Lfield (root_exp, fld, root_exp_typ) -> (
match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with
| Some access_path -> | Some access_path ->
AccessPath access_path AccessExpression (AccessExpression.of_access_path access_path)
| None -> | None ->
(* unsupported field expression: represent with a dummy variable *) (* unsupported field expression: represent with a dummy variable *)
of_sil_ of_sil_
@ -153,7 +153,7 @@ let of_sil ~include_array_indexes ~f_resolve_id exp typ =
| Lindex (root_exp, index_exp) -> ( | Lindex (root_exp, index_exp) -> (
match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with
| Some access_path -> | Some access_path ->
AccessPath access_path AccessExpression (AccessExpression.of_access_path access_path)
| None -> | None ->
(* unsupported index expression: represent with a dummy variable *) (* unsupported index expression: represent with a dummy variable *)
of_sil_ of_sil_
@ -163,7 +163,7 @@ let of_sil ~include_array_indexes ~f_resolve_id exp typ =
| Lvar _ -> | Lvar _ ->
match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with match AccessPath.of_lhs_exp ~include_array_indexes exp typ ~f_resolve_id with
| Some access_path -> | Some access_path ->
AccessPath access_path AccessExpression (AccessExpression.of_access_path access_path)
| None -> | None ->
L.(die InternalError) "Couldn't convert var expression %a to access path" Exp.pp exp L.(die InternalError) "Couldn't convert var expression %a to access path" Exp.pp exp
in in

@ -11,7 +11,7 @@ open! IStd
module F = Format module F = Format
type t = type t =
| AccessPath of AccessPath.t (** access path (e.g., x.f.g or x[i]) *) | AccessExpression of AccessExpression.t (** access path (e.g., x.f.g or x[i]) *)
| UnaryOperator of Unop.t * t * Typ.t option | UnaryOperator of Unop.t * t * Typ.t option
(** Unary operator with type of the result if known *) (** Unary operator with type of the result if known *)
| BinaryOperator of Binop.t * t * t (** Binary operator *) | BinaryOperator of Binop.t * t * t (** Binary operator *)

@ -48,8 +48,8 @@ let of_sil ~include_array_indexes ~f_resolve_id (instr: Sil.instr) =
let analyze_id_assignment lhs_id rhs_exp rhs_typ loc = let analyze_id_assignment lhs_id rhs_exp rhs_typ loc =
let rhs_hil_exp = exp_of_sil rhs_exp rhs_typ in let rhs_hil_exp = exp_of_sil rhs_exp rhs_typ in
match rhs_hil_exp with match rhs_hil_exp with
| AccessPath rhs_access_path -> | AccessExpression rhs_access_expr ->
Bind (lhs_id, rhs_access_path) Bind (lhs_id, AccessExpression.to_access_path rhs_access_expr)
| _ -> | _ ->
Instr (Assign (((lhs_id, rhs_typ), []), rhs_hil_exp, loc)) Instr (Assign (((lhs_id, rhs_typ), []), rhs_hil_exp, loc))
in in
@ -69,8 +69,8 @@ let of_sil ~include_array_indexes ~f_resolve_id (instr: Sil.instr) =
| Store (lhs_exp, typ, rhs_exp, loc) -> | Store (lhs_exp, typ, rhs_exp, loc) ->
let lhs_access_path = let lhs_access_path =
match exp_of_sil lhs_exp typ with match exp_of_sil lhs_exp typ with
| AccessPath ap -> | AccessExpression access_expr ->
ap AccessExpression.to_access_path access_expr
| BinaryOperator (_, exp0, exp1) -> ( | BinaryOperator (_, exp0, exp1) -> (
match match
(* pointer arithmetic. somewhere in one of the expressions, there should be at least (* pointer arithmetic. somewhere in one of the expressions, there should be at least
@ -107,8 +107,8 @@ let of_sil ~include_array_indexes ~f_resolve_id (instr: Sil.instr) =
match exp_of_sil call_exp (Typ.mk Tvoid) with match exp_of_sil call_exp (Typ.mk Tvoid) with
| Constant Cfun procname | Closure (procname, _) -> | Constant Cfun procname | Closure (procname, _) ->
Direct procname Direct procname
| AccessPath access_path -> | AccessExpression access_expr ->
Indirect access_path Indirect (AccessExpression.to_access_path access_expr)
| call_exp -> | call_exp ->
L.(die InternalError) "Unexpected call expression %a" HilExp.pp call_exp L.(die InternalError) "Unexpected call expression %a" HilExp.pp call_exp
in in

@ -70,7 +70,10 @@ struct
(fun id access_path astate_acc -> (fun id access_path astate_acc ->
let lhs_access_path = ((id, Typ.mk Typ.Tvoid), []) in let lhs_access_path = ((id, Typ.mk Typ.Tvoid), []) in
let dummy_assign = let dummy_assign =
HilInstr.Assign (lhs_access_path, HilExp.AccessPath access_path, loc) HilInstr.Assign
( lhs_access_path
, HilExp.AccessExpression (AccessExpression.of_access_path access_path)
, loc )
in in
TransferFunctions.exec_instr astate_acc extras node dummy_assign ) TransferFunctions.exec_instr astate_acc extras node dummy_assign )
id_map actual_state id_map actual_state

@ -194,8 +194,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
match Var.get_footprint_index var with match Var.get_footprint_index var with
| Some footprint_index -> ( | Some footprint_index -> (
match List.nth actuals footprint_index with match List.nth actuals footprint_index with
| Some HilExp.AccessPath actual_access_path -> | Some HilExp.AccessExpression actual_access_expr ->
Some (Domain.LocalAccessPath.make actual_access_path caller_pname) Some
(Domain.LocalAccessPath.make
(AccessExpression.to_access_path actual_access_expr)
caller_pname)
| _ -> | _ ->
None ) None )
| None -> | None ->
@ -218,11 +221,13 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| Call | Call
( (Some return_base as ret_opt) ( (Some return_base as ret_opt)
, Direct (Typ.Procname.Java java_callee_procname as callee_procname) , Direct (Typ.Procname.Java java_callee_procname as callee_procname)
, ((HilExp.AccessPath receiver_ap) :: _ as actuals) , ((HilExp.AccessExpression receiver_ae) :: _ as actuals)
, _ , _
, _ ) -> , _ ) ->
let summary = Summary.read_summary proc_data.pdesc callee_procname in let summary = Summary.read_summary proc_data.pdesc callee_procname in
let receiver = Domain.LocalAccessPath.make receiver_ap caller_pname in let receiver =
Domain.LocalAccessPath.make (AccessExpression.to_access_path receiver_ae) caller_pname
in
if ( LithoFramework.is_component_builder callee_procname proc_data.tenv if ( LithoFramework.is_component_builder callee_procname proc_data.tenv
(* track Builder's in order to check required prop's *) (* track Builder's in order to check required prop's *)
|| GraphQLGetters.is_function callee_procname summary || GraphQLGetters.is_function callee_procname summary
@ -247,13 +252,15 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| Call (ret_opt, Direct callee_procname, actuals, _, _) -> | Call (ret_opt, Direct callee_procname, actuals, _, _) ->
let summary = Summary.read_summary proc_data.pdesc callee_procname in let summary = Summary.read_summary proc_data.pdesc callee_procname in
apply_callee_summary summary caller_pname ret_opt actuals astate apply_callee_summary summary caller_pname ret_opt actuals astate
| Assign (lhs_ap, HilExp.AccessPath rhs_ap, _) | Assign (lhs_ap, HilExp.AccessExpression rhs_ae, _)
-> ( -> (
(* creating an alias for the rhs binding; assume all reads will now occur through the (* creating an alias for the rhs binding; assume all reads will now occur through the
alias. this helps us keep track of chains in cases like tmp = getFoo(); x = tmp; alias. this helps us keep track of chains in cases like tmp = getFoo(); x = tmp;
tmp.getBar() *) tmp.getBar() *)
let lhs_access_path = Domain.LocalAccessPath.make lhs_ap caller_pname in let lhs_access_path = Domain.LocalAccessPath.make lhs_ap caller_pname in
let rhs_access_path = Domain.LocalAccessPath.make rhs_ap caller_pname in let rhs_access_path =
Domain.LocalAccessPath.make (AccessExpression.to_access_path rhs_ae) caller_pname
in
try try
let call_set = Domain.find rhs_access_path astate in let call_set = Domain.find rhs_access_path astate in
Domain.remove rhs_access_path astate |> Domain.add lhs_access_path call_set Domain.remove rhs_access_path astate |> Domain.add lhs_access_path call_set

@ -219,7 +219,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
astate astate
| [arg] when HilExp.is_null_literal arg -> | [arg] when HilExp.is_null_literal arg ->
astate astate
| (HilExp.AccessPath ap) :: other_args -> | (HilExp.AccessExpression access_expr) :: other_args ->
let ap = AccessExpression.to_access_path access_expr in
check_nil_in_objc_container proc_data loc other_args (check_ap proc_data loc ap astate) check_nil_in_objc_container proc_data loc other_args (check_ap proc_data loc ap astate)
| _ :: other_args -> | _ :: other_args ->
check_nil_in_objc_container proc_data loc other_args astate check_nil_in_objc_container proc_data loc other_args astate
@ -245,9 +246,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
when NullCheckedPname.mem callee_pname checked_pnames -> when NullCheckedPname.mem callee_pname checked_pnames ->
(* Do not report nullable when the method has already been checked for null *) (* Do not report nullable when the method has already been checked for null *)
remove_nullable_ap (ret_var, []) astate remove_nullable_ap (ret_var, []) astate
| Call (_, Direct callee_pname, (HilExp.AccessPath receiver) :: _, _, _) | Call (_, Direct callee_pname, (HilExp.AccessExpression receiver) :: _, _, _)
when Models.is_check_not_null callee_pname -> when Models.is_check_not_null callee_pname ->
assume_pnames_notnull receiver astate assume_pnames_notnull (AccessExpression.to_access_path receiver) astate
| Call (_, Direct callee_pname, _, _, _) when is_blacklisted_method callee_pname -> | Call (_, Direct callee_pname, _, _, _) when is_blacklisted_method callee_pname ->
astate astate
| Call (Some ret_var, Direct callee_pname, _, _, loc) | Call (Some ret_var, Direct callee_pname, _, _, loc)
@ -255,19 +256,19 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
~attrs_of_pname:Specs.proc_resolve_attributes Annotations.ia_is_nullable -> ~attrs_of_pname:Specs.proc_resolve_attributes Annotations.ia_is_nullable ->
let call_site = CallSite.make callee_pname loc in let call_site = CallSite.make callee_pname loc in
add_nullable_ap (ret_var, []) (CallSites.singleton call_site) astate add_nullable_ap (ret_var, []) (CallSites.singleton call_site) astate
| Call (_, Direct callee_pname, (HilExp.AccessPath receiver) :: _, _, loc) | Call (_, Direct callee_pname, (HilExp.AccessExpression receiver) :: _, _, loc)
when is_non_objc_instance_method callee_pname -> when is_non_objc_instance_method callee_pname ->
check_ap proc_data loc receiver astate check_ap proc_data loc (AccessExpression.to_access_path receiver) astate
| Call (_, Direct callee_pname, args, _, loc) when is_objc_container_add_method callee_pname -> | Call (_, Direct callee_pname, args, _, loc) when is_objc_container_add_method callee_pname ->
check_nil_in_objc_container proc_data loc args astate check_nil_in_objc_container proc_data loc args astate
| Call | Call
( Some ((_, ret_typ) as ret_var) ( Some ((_, ret_typ) as ret_var)
, Direct callee_pname , Direct callee_pname
, (HilExp.AccessPath receiver) :: _ , (HilExp.AccessExpression receiver) :: _
, _ , _
, _ ) , _ )
when Typ.is_pointer ret_typ && is_objc_instance_method callee_pname -> ( when Typ.is_pointer ret_typ && is_objc_instance_method callee_pname -> (
match longest_nullable_prefix receiver astate with match longest_nullable_prefix (AccessExpression.to_access_path receiver) astate with
| None -> | None ->
astate astate
| Some (_, call_sites) -> | Some (_, call_sites) ->
@ -285,9 +286,10 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
report_nullable_dereference nullable_ap call_sites proc_data loc ) report_nullable_dereference nullable_ap call_sites proc_data loc )
(longest_nullable_prefix lhs astate) ; (longest_nullable_prefix lhs astate) ;
match rhs with match rhs with
| HilExp.AccessPath ap -> ( | HilExp.AccessExpression access_expr -> (
try try
(* Add the lhs to the list of nullable values if the rhs is nullable *) (* Add the lhs to the list of nullable values if the rhs is nullable *)
let ap = AccessExpression.to_access_path access_expr in
add_nullable_ap lhs (find_nullable_ap ap astate) astate add_nullable_ap lhs (find_nullable_ap ap astate) astate
with Not_found -> with Not_found ->
(* Remove the lhs from the list of nullable values if the rhs is not nullable *) (* Remove the lhs from the list of nullable values if the rhs is not nullable *)
@ -295,24 +297,26 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| _ -> | _ ->
(* Remove the lhs from the list of nullable values if the rhs is not an access path *) (* Remove the lhs from the list of nullable values if the rhs is not an access path *)
remove_nullable_ap lhs astate ) remove_nullable_ap lhs astate )
| Assume (HilExp.AccessPath ap, _, _, _) -> | Assume (HilExp.AccessExpression access_expr, _, _, _) ->
assume_pnames_notnull ap astate assume_pnames_notnull (AccessExpression.to_access_path access_expr) astate
| Assume | Assume
( ( HilExp.BinaryOperator (Binop.Ne, HilExp.AccessPath ap, exp) ( ( HilExp.BinaryOperator (Binop.Ne, HilExp.AccessExpression access_expr, exp)
| HilExp.BinaryOperator (Binop.Ne, exp, HilExp.AccessPath ap) ) | HilExp.BinaryOperator (Binop.Ne, exp, HilExp.AccessExpression access_expr) )
, _ , _
, _ , _
, _ ) , _ )
| Assume | Assume
( HilExp.UnaryOperator ( HilExp.UnaryOperator
( Unop.LNot ( Unop.LNot
, ( HilExp.BinaryOperator (Binop.Eq, HilExp.AccessPath ap, exp) , ( HilExp.BinaryOperator (Binop.Eq, HilExp.AccessExpression access_expr, exp)
| HilExp.BinaryOperator (Binop.Eq, exp, HilExp.AccessPath ap) ) | HilExp.BinaryOperator (Binop.Eq, exp, HilExp.AccessExpression access_expr) )
, _ ) , _ )
, _ , _
, _ , _
, _ ) -> , _ ) ->
if HilExp.is_null_literal exp then assume_pnames_notnull ap astate else astate if HilExp.is_null_literal exp then
assume_pnames_notnull (AccessExpression.to_access_path access_expr) astate
else astate
| _ -> | _ ->
astate astate
end end

@ -57,8 +57,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
match exp with match exp with
| HilExp.Constant Cint n when IntLit.isnull n -> | HilExp.Constant Cint n when IntLit.isnull n ->
Some (UseDefChain.NullDefAssign (loc, lhs)) Some (UseDefChain.NullDefAssign (loc, lhs))
| HilExp.AccessPath ap -> ( | HilExp.AccessExpression access_expr -> (
try try
let ap = AccessExpression.to_access_path access_expr in
match Domain.find ap astate with match Domain.find ap astate with
| UseDefChain.NullDefCompare _ -> | UseDefChain.NullDefCompare _ ->
(* Stop NullDefCompare from propagating here because we want to prevent (* Stop NullDefCompare from propagating here because we want to prevent
@ -73,9 +74,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let extract_null_compare_expr = function let extract_null_compare_expr = function
| HilExp.BinaryOperator ((Eq | Ne), HilExp.AccessPath ap, exp) | HilExp.BinaryOperator ((Eq | Ne), HilExp.AccessExpression access_expr, exp)
| HilExp.BinaryOperator ((Eq | Ne), exp, HilExp.AccessPath ap) -> | HilExp.BinaryOperator ((Eq | Ne), exp, HilExp.AccessExpression access_expr) ->
Option.some_if (HilExp.is_null_literal exp) ap Option.some_if (HilExp.is_null_literal exp) (AccessExpression.to_access_path access_expr)
| _ -> | _ ->
None None

@ -96,10 +96,12 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
List.iteri List.iteri
~f:(fun idx e -> ~f:(fun idx e ->
match e with match e with
| HilExp.AccessPath ((var, t), al) | HilExp.AccessExpression access_expr ->
when should_report_var pdesc tenv uninit_vars ((var, t), al) && not (Typ.is_pointer t) let (var, t), al = AccessExpression.to_access_path access_expr in
&& not (is_struct_field_passed_by_ref call t al idx) -> if should_report_var pdesc tenv uninit_vars ((var, t), al) && not (Typ.is_pointer t)
report_intra ((var, t), al) loc (snd extras) && not (is_struct_field_passed_by_ref call t al idx)
then report_intra ((var, t), al) loc (snd extras)
else ()
| _ -> | _ ->
() ) () )
actuals actuals
@ -205,7 +207,11 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
else astate.prepost else astate.prepost
in in
match instr with match instr with
| Assign ((((lhs_var, lhs_typ), apl) as lhs_ap), (HilExp.AccessPath (rhs_base, al) as rhs), loc) -> | Assign
( (((lhs_var, lhs_typ), apl) as lhs_ap)
, (HilExp.AccessExpression access_expr as rhs_expr)
, loc ) ->
let rhs_base, al = AccessExpression.to_access_path access_expr in
let uninit_vars' = D.remove lhs_ap astate.uninit_vars in let uninit_vars' = D.remove lhs_ap astate.uninit_vars in
let uninit_vars = let uninit_vars =
if Int.equal (List.length apl) 0 then if Int.equal (List.length apl) 0 then
@ -213,7 +219,7 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
remove_all_fields tenv (lhs_var, lhs_typ) uninit_vars' remove_all_fields tenv (lhs_var, lhs_typ) uninit_vars'
else uninit_vars' else uninit_vars'
in in
let prepost = update_prepost lhs_ap rhs in let prepost = update_prepost lhs_ap rhs_expr in
(* check on lhs_typ to avoid false positive when assigning a pointer to another *) (* check on lhs_typ to avoid false positive when assigning a pointer to another *)
if should_report_var pdesc tenv uninit_vars (rhs_base, al) && not (Typ.is_pointer lhs_typ) if should_report_var pdesc tenv uninit_vars (rhs_base, al) && not (Typ.is_pointer lhs_typ)
then report_intra (rhs_base, al) loc (snd extras) ; then report_intra (rhs_base, al) loc (snd extras) ;
@ -234,23 +240,25 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
List.foldi List.foldi
~f:(fun idx acc actual_exp -> ~f:(fun idx acc actual_exp ->
match actual_exp with match actual_exp with
| HilExp.AccessPath (((_, {Typ.desc= Tarray _}) as base), al) | HilExp.AccessExpression access_expr -> (
when is_blacklisted_function call -> match AccessExpression.to_access_path access_expr with
| ((_, {Typ.desc= Tarray _}) as base), al when is_blacklisted_function call ->
D.remove (base, al) acc D.remove (base, al) acc
| HilExp.AccessPath (((_, t) as base), al) | ((_, t) as base), al when is_struct_field_passed_by_ref call t al idx ->
when is_struct_field_passed_by_ref call t al idx ->
(* Access to a field of a struct by reference *) (* Access to a field of a struct by reference *)
if Config.uninit_interproc then if Config.uninit_interproc then
remove_initialized_params pdesc call acc idx (base, al) false remove_initialized_params pdesc call acc idx (base, al) false
else D.remove (base, al) acc else D.remove (base, al) acc
| HilExp.AccessPath ap when Typ.Procname.is_constructor call -> | ap when Typ.Procname.is_constructor call ->
remove_all_fields tenv (fst ap) (D.remove ap acc) remove_all_fields tenv (fst ap) (D.remove ap acc)
| HilExp.AccessPath (((_, {Typ.desc= Tptr _}) as base), al) -> | ((_, {Typ.desc= Tptr _}) as base), al ->
if Config.uninit_interproc then if Config.uninit_interproc then
remove_initialized_params pdesc call acc idx (base, al) true remove_initialized_params pdesc call acc idx (base, al) true
else else
let acc' = D.remove (base, al) acc in let acc' = D.remove (base, al) acc in
remove_all_fields tenv base acc' remove_all_fields tenv base acc'
| _ ->
acc )
| HilExp.Closure (_, apl) -> | HilExp.Closure (_, apl) ->
(* remove the captured variables of a block/lambda *) (* remove the captured variables of a block/lambda *)
List.fold ~f:(fun acc' (base, _) -> D.remove (base, []) acc') ~init:acc apl List.fold ~f:(fun acc' (base, _) -> D.remove (base, []) acc') ~init:acc apl

@ -33,8 +33,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let open HilExp in let open HilExp in
let open Domain in let open Domain in
match e with match e with
| HilExp.AccessPath ap -> ( | HilExp.AccessExpression access_expr -> (
try AttributeMapDomain.find ap attribute_map with Not_found -> AttributeSetDomain.empty ) try AttributeMapDomain.find (AccessExpression.to_access_path access_expr) attribute_map
with Not_found -> AttributeSetDomain.empty )
| Constant _ -> | Constant _ ->
AttributeSetDomain.of_list [Attribute.Functional] AttributeSetDomain.of_list [Attribute.Functional]
| Exception expr (* treat exceptions as transparent wrt attributes *) | Cast (_, expr) -> | Exception expr (* treat exceptions as transparent wrt attributes *) | Cast (_, expr) ->
@ -53,8 +54,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let open Domain in let open Domain in
let open HilExp in let open HilExp in
match expr with match expr with
| AccessPath ap -> | AccessExpression access_expr ->
OwnershipDomain.get_owned ap ownership OwnershipDomain.get_owned (AccessExpression.to_access_path access_expr) ownership
| Constant _ -> | Constant _ ->
OwnershipAbstractValue.owned OwnershipAbstractValue.owned
| Exception e (* treat exceptions as transparent wrt ownership *) | Cast (_, e) -> | Exception e (* treat exceptions as transparent wrt ownership *) | Cast (_, e) ->
@ -102,7 +103,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let ret_access_path = (ret, []) in let ret_access_path = (ret, []) in
let get_ownership formal_index acc = let get_ownership formal_index acc =
match List.nth actuals formal_index with match List.nth actuals formal_index with
| Some HilExp.AccessPath actual_ap -> | Some HilExp.AccessExpression access_expr ->
let actual_ap = AccessExpression.to_access_path access_expr in
OwnershipDomain.get_owned actual_ap ownership |> OwnershipAbstractValue.join acc OwnershipDomain.get_owned actual_ap ownership |> OwnershipAbstractValue.join acc
| Some HilExp.Constant _ -> | Some HilExp.Constant _ ->
acc acc
@ -376,8 +378,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
let open RacerDConfig in let open RacerDConfig in
let get_receiver_ap actuals = let get_receiver_ap actuals =
match List.hd actuals with match List.hd actuals with
| Some HilExp.AccessPath receiver_ap -> | Some HilExp.AccessExpression receiver_expr ->
receiver_ap AccessExpression.to_access_path receiver_expr
| _ -> | _ ->
L.(die InternalError) L.(die InternalError)
"Call to %a is marked as a container write, but has no receiver" Typ.Procname.pp "Call to %a is marked as a container write, but has no receiver" Typ.Procname.pp
@ -428,8 +430,8 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
if AccessDomain.is_empty accesses then accesses if AccessDomain.is_empty accesses then accesses
else else
let rec get_access_path = function let rec get_access_path = function
| HilExp.AccessPath ap -> | HilExp.AccessExpression access_expr ->
Some ap Some (AccessExpression.to_access_path access_expr)
| HilExp.Cast (_, e) | HilExp.Exception e -> | HilExp.Cast (_, e) | HilExp.Exception e ->
get_access_path e get_access_path e
| _ -> | _ ->
@ -479,8 +481,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
| HilExp.Constant _ -> | HilExp.Constant _ ->
(* the actual is a constant, so it's owned in the caller. *) (* the actual is a constant, so it's owned in the caller. *)
Conjunction actual_indexes Conjunction actual_indexes
| HilExp.AccessPath actual_access_path | HilExp.AccessExpression access_expr
-> ( -> (
let actual_access_path = AccessExpression.to_access_path access_expr in
if OwnershipDomain.is_owned actual_access_path caller_astate.ownership then if OwnershipDomain.is_owned actual_access_path caller_astate.ownership then
(* the actual passed to the current callee is owned. drop all the conditional accesses (* the actual passed to the current callee is owned. drop all the conditional accesses
for that actual, since they're all safe *) for that actual, since they're all safe *)
@ -661,15 +664,18 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
in in
if is_box callee_pname then if is_box callee_pname then
match (ret_opt, actuals) with match (ret_opt, actuals) with
| Some ret, (HilExp.AccessPath actual_ap) :: _ | Some ret, (HilExp.AccessExpression actual_access_expr) :: _ ->
when AttributeMapDomain.has_attribute actual_ap Functional let actual_ap = AccessExpression.to_access_path actual_access_expr in
astate.attribute_map -> if AttributeMapDomain.has_attribute actual_ap Functional
astate.attribute_map
then
(* TODO: check for constants, which are functional? *) (* TODO: check for constants, which are functional? *)
let attribute_map = let attribute_map =
AttributeMapDomain.add_attribute (ret, []) Functional AttributeMapDomain.add_attribute (ret, []) Functional
astate.attribute_map astate.attribute_map
in in
{astate with attribute_map} {astate with attribute_map}
else astate
| _ -> | _ ->
astate astate
else if should_assume_returns_ownership call_flags actuals then else if should_assume_returns_ownership call_flags actuals then
@ -733,8 +739,10 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
report spurious read/write races *) report spurious read/write races *)
rhs_accesses rhs_accesses
else else
add_access (AccessPath lhs_access_path) loc ~is_write_access:true rhs_accesses add_access
astate.locks astate.threads astate.ownership proc_data (AccessExpression (AccessExpression.of_access_path lhs_access_path))
loc ~is_write_access:true rhs_accesses astate.locks astate.threads astate.ownership
proc_data
in in
let ownership = propagate_ownership lhs_access_path rhs_exp astate.ownership in let ownership = propagate_ownership lhs_access_path rhs_exp astate.ownership in
let attribute_map = propagate_attributes lhs_access_path rhs_exp astate.attribute_map in let attribute_map = propagate_attributes lhs_access_path rhs_exp astate.attribute_map in
@ -750,8 +758,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct
[var] is set to true. return None if it has free variables that stop us from [var] is set to true. return None if it has free variables that stop us from
evaluating it *) evaluating it *)
and eval_bexp var = function and eval_bexp var = function
| HilExp.AccessPath ap when AccessPath.equal ap var -> | HilExp.AccessExpression access_expr ->
Some true if AccessPath.equal (AccessExpression.to_access_path access_expr) var then Some true
else None
| HilExp.Constant c -> | HilExp.Constant c ->
Some (not (Const.iszero_int_float c)) Some (not (Const.iszero_int_float c))
| HilExp.UnaryOperator (Unop.LNot, e, _) -> | HilExp.UnaryOperator (Unop.LNot, e, _) ->

@ -35,15 +35,17 @@ include TaintAnalysis.Make (struct
| None, _ when Typ.Procname.is_constructor pname -> | None, _ when Typ.Procname.is_constructor pname ->
(* "this" is always the first arg of a constructor; propagate taint there *) (* "this" is always the first arg of a constructor; propagate taint there *)
[TaintSpec.Propagate_to_receiver] [TaintSpec.Propagate_to_receiver]
| ( None | None, (HilExp.AccessExpression access_expr) :: _ -> (
, (HilExp.AccessPath ((Var.ProgramVar pvar, {desc= Typ.Tptr (_, Typ.Pk_pointer)}), [])) match AccessExpression.to_access_path access_expr with
:: _ ) | (Var.ProgramVar pvar, {desc= Typ.Tptr (_, Typ.Pk_pointer)}), []
when Pvar.is_frontend_tmp pvar -> when Pvar.is_frontend_tmp pvar ->
(* no return value, but the frontend has introduced a dummy return variable and will (* no return value, but the frontend has introduced a dummy return variable and will
assign the return value to this variable. So propagate taint to the dummy return assign the return value to this variable. So propagate taint to the dummy return
variable *) variable *)
let actual_index = List.length actuals - 1 in let actual_index = List.length actuals - 1 in
[TaintSpec.Propagate_to_actual actual_index] [TaintSpec.Propagate_to_actual actual_index]
| _ ->
[TaintSpec.Propagate_to_receiver] )
| None, _ -> | None, _ ->
(* no return value; propagate taint from actuals to receiver *) (* no return value; propagate taint from actuals to receiver *)
[TaintSpec.Propagate_to_receiver] [TaintSpec.Propagate_to_receiver]

@ -92,7 +92,9 @@ module SourceKind = struct
in in
(* accessed global will be passed to us as the only parameter *) (* accessed global will be passed to us as the only parameter *)
match actuals with match actuals with
| [(HilExp.AccessPath access_path)] when is_gflag access_path -> | [(HilExp.AccessExpression access_expr)] ->
let access_path = AccessExpression.to_access_path access_expr in
if is_gflag access_path then
let (global_pvar, _), _ = access_path in let (global_pvar, _), _ = access_path in
let typ_desc = let typ_desc =
match AccessPath.get_typ access_path tenv with match AccessPath.get_typ access_path tenv with
@ -102,6 +104,7 @@ module SourceKind = struct
Typ.void_star.desc Typ.void_star.desc
in in
Some (CommandLineFlag (global_pvar, typ_desc), None) Some (CommandLineFlag (global_pvar, typ_desc), None)
else None
| _ -> | _ ->
None ) None )
| Typ.Procname.C _ -> ( | Typ.Procname.C _ -> (

@ -26,8 +26,8 @@ include TaintAnalysis.Make (struct
let handle_unknown_call pname ret_typ_opt actuals tenv = let handle_unknown_call pname ret_typ_opt actuals tenv =
let get_receiver_typ tenv = function let get_receiver_typ tenv = function
| HilExp.AccessPath access_path -> | HilExp.AccessExpression access_expr ->
AccessPath.get_typ access_path tenv AccessPath.get_typ (AccessExpression.to_access_path access_expr) tenv
| _ -> | _ ->
None None
in in

@ -123,7 +123,9 @@ module SourceKind = struct
| Typ.Procname.C _ when Typ.Procname.equal pname BuiltinDecl.__global_access -> ( | Typ.Procname.C _ when Typ.Procname.equal pname BuiltinDecl.__global_access -> (
match (* accessed global will be passed to us as the only parameter *) match (* accessed global will be passed to us as the only parameter *)
actuals with actuals with
| [(HilExp.AccessPath ((Var.ProgramVar pvar, _), _))] -> | [(HilExp.AccessExpression access_expr)] -> (
match AccessExpression.to_access_path access_expr with
| (Var.ProgramVar pvar, _), _ ->
let pvar_string = Pvar.to_string pvar in let pvar_string = Pvar.to_string pvar in
(* checking substring instead of prefix because we expect field names like (* checking substring instead of prefix because we expect field names like
com.myapp.R$drawable.whatever *) com.myapp.R$drawable.whatever *)
@ -132,6 +134,8 @@ module SourceKind = struct
else None else None
| _ -> | _ ->
None ) None )
| _ ->
None )
| pname when BuiltinDecl.is_declared pname -> | pname when BuiltinDecl.is_declared pname ->
None None
| pname -> | pname ->

@ -74,8 +74,10 @@ module Make (TaintSpecification : TaintSpec.S) = struct
(* get the node associated with [exp] in [access_tree] *) (* get the node associated with [exp] in [access_tree] *)
let rec hil_exp_get_node ?(abstracted= false) (exp: HilExp.t) access_tree proc_data = let rec hil_exp_get_node ?(abstracted= false) (exp: HilExp.t) access_tree proc_data =
match exp with match exp with
| AccessPath access_path -> | AccessExpression access_expr ->
exp_get_node_ ~abstracted access_path access_tree proc_data exp_get_node_ ~abstracted
(AccessExpression.to_access_path access_expr)
access_tree proc_data
| Cast (_, e) | Exception e | UnaryOperator (_, e, _) -> | Cast (_, e) | Exception e | UnaryOperator (_, e, _) ->
hil_exp_get_node ~abstracted e access_tree proc_data hil_exp_get_node ~abstracted e access_tree proc_data
| BinaryOperator (_, e1, e2) -> ( | BinaryOperator (_, e1, e2) -> (
@ -99,8 +101,10 @@ module Make (TaintSpecification : TaintSpec.S) = struct
let add_actual_source source index actuals access_tree proc_data = let add_actual_source source index actuals access_tree proc_data =
match List.nth_exn actuals index with match List.nth_exn actuals index with
| HilExp.AccessPath actual_ap_raw -> | HilExp.AccessExpression actual_ae_raw ->
let actual_ap = AccessPath.Abs.Abstracted actual_ap_raw in let actual_ap =
AccessPath.Abs.Abstracted (AccessExpression.to_access_path actual_ae_raw)
in
let trace = access_path_get_trace actual_ap access_tree proc_data in let trace = access_path_get_trace actual_ap access_tree proc_data in
TaintDomain.add_trace actual_ap (TraceDomain.add_source source trace) access_tree TaintDomain.add_trace actual_ap (TraceDomain.add_source source trace) access_tree
| _ -> | _ ->
@ -332,11 +336,13 @@ module Make (TaintSpecification : TaintSpec.S) = struct
let actual_trace' = TraceDomain.add_sink sink' actual_trace in let actual_trace' = TraceDomain.add_sink sink' actual_trace in
report_trace actual_trace' callee_site proc_data ; report_trace actual_trace' callee_site proc_data ;
match exp with match exp with
| HilExp.AccessPath actual_ap_raw | HilExp.AccessExpression actual_ae_raw
when not when not
(TraceDomain.Sources.Footprint.is_empty (TraceDomain.Sources.Footprint.is_empty
(TraceDomain.sources actual_trace').footprint) -> (TraceDomain.sources actual_trace').footprint) ->
let actual_ap = AccessPath.Abs.Abstracted actual_ap_raw in let actual_ap =
AccessPath.Abs.Abstracted (AccessExpression.to_access_path actual_ae_raw)
in
TaintDomain.add_trace actual_ap actual_trace' access_tree_acc TaintDomain.add_trace actual_ap actual_trace' access_tree_acc
| _ -> | _ ->
(* no more sources can flow into this sink; no sense in keeping track of it *) (* no more sources can flow into this sink; no sense in keeping track of it *)
@ -386,8 +392,10 @@ module Make (TaintSpecification : TaintSpec.S) = struct
(projected_ap_opt, Option.value ~default:TaintDomain.empty_node caller_node_opt) (projected_ap_opt, Option.value ~default:TaintDomain.empty_node caller_node_opt)
| Var.LogicalVar id when Ident.is_footprint id -> ( | Var.LogicalVar id when Ident.is_footprint id -> (
match List.nth actuals (Ident.get_stamp id) with match List.nth actuals (Ident.get_stamp id) with
| Some HilExp.AccessPath actual_ap -> | Some HilExp.AccessExpression actual_ae ->
let projected_ap = project ~formal_ap ~actual_ap in let projected_ap =
project ~formal_ap ~actual_ap:(AccessExpression.to_access_path actual_ae)
in
let caller_node_opt = access_path_get_node projected_ap access_tree proc_data in let caller_node_opt = access_path_get_node projected_ap access_tree proc_data in
(Some projected_ap, Option.value ~default:TaintDomain.empty_node caller_node_opt) (Some projected_ap, Option.value ~default:TaintDomain.empty_node caller_node_opt)
| Some exp -> | Some exp ->
@ -448,7 +456,10 @@ module Make (TaintSpecification : TaintSpec.S) = struct
| AccessPath.ArrayAccess (_, indexes) -> | AccessPath.ArrayAccess (_, indexes) ->
let dummy_call_site = CallSite.make BuiltinDecl.__array_access loc in let dummy_call_site = CallSite.make BuiltinDecl.__array_access loc in
let dummy_actuals = let dummy_actuals =
List.map ~f:(fun index_ap -> HilExp.AccessPath index_ap) indexes List.map
~f:(fun index_ap ->
HilExp.AccessExpression (AccessExpression.of_access_path index_ap) )
indexes
in in
let sinks = let sinks =
TraceDomain.Sink.get dummy_call_site dummy_actuals proc_data.ProcData.tenv TraceDomain.Sink.get dummy_call_site dummy_actuals proc_data.ProcData.tenv
@ -464,7 +475,11 @@ module Make (TaintSpecification : TaintSpec.S) = struct
let add_sources_for_access_path (((var, _), _) as ap) loc astate = let add_sources_for_access_path (((var, _), _) as ap) loc astate =
if Var.is_global var then if Var.is_global var then
let dummy_call_site = CallSite.make BuiltinDecl.__global_access loc in let dummy_call_site = CallSite.make BuiltinDecl.__global_access loc in
match TraceDomain.Source.get dummy_call_site [HilExp.AccessPath ap] proc_data.tenv with match
TraceDomain.Source.get dummy_call_site
[HilExp.AccessExpression (AccessExpression.of_access_path ap)]
proc_data.tenv
with
| Some {TraceDomain.Source.source} -> | Some {TraceDomain.Source.source} ->
let access_path = AccessPath.Abs.Exact ap in let access_path = AccessPath.Abs.Exact ap in
let trace, subtree = let trace, subtree =
@ -479,7 +494,8 @@ module Make (TaintSpecification : TaintSpec.S) = struct
in in
let add_sources_sinks_for_exp exp loc astate = let add_sources_sinks_for_exp exp loc astate =
match exp with match exp with
| HilExp.AccessPath access_path -> | HilExp.AccessExpression access_expr ->
let access_path = AccessExpression.to_access_path access_expr in
add_sinks_for_access_path access_path loc astate add_sinks_for_access_path access_path loc astate
|> add_sources_for_access_path access_path loc |> add_sources_for_access_path access_path loc
| _ -> | _ ->
@ -572,15 +588,17 @@ module Make (TaintSpecification : TaintSpec.S) = struct
propagate_to_access_path (AccessPath.Abs.Abstracted (ret_ap, [])) actuals propagate_to_access_path (AccessPath.Abs.Abstracted (ret_ap, [])) actuals
astate_acc astate_acc
| ( TaintSpec.Propagate_to_receiver | ( TaintSpec.Propagate_to_receiver
, (AccessPath receiver_ap) :: (_ :: _ as other_actuals) , (AccessExpression receiver_ae) :: (_ :: _ as other_actuals)
, _ ) -> , _ ) ->
propagate_to_access_path (AccessPath.Abs.Abstracted receiver_ap) other_actuals propagate_to_access_path
astate_acc (AccessPath.Abs.Abstracted (AccessExpression.to_access_path receiver_ae))
other_actuals astate_acc
| TaintSpec.Propagate_to_actual actual_index, _, _ -> ( | TaintSpec.Propagate_to_actual actual_index, _, _ -> (
match List.nth actuals actual_index with match List.nth actuals actual_index with
| Some HilExp.AccessPath actual_ap -> | Some HilExp.AccessExpression actual_ae ->
propagate_to_access_path (AccessPath.Abs.Abstracted actual_ap) actuals propagate_to_access_path
astate_acc (AccessPath.Abs.Abstracted (AccessExpression.to_access_path actual_ae))
actuals astate_acc
| _ -> | _ ->
astate_acc ) astate_acc )
| _ -> | _ ->
@ -593,20 +611,27 @@ module Make (TaintSpecification : TaintSpec.S) = struct
| "operator=" when not (Typ.Procname.is_java callee_pname) -> ( | "operator=" when not (Typ.Procname.is_java callee_pname) -> (
match (* treat unknown calls to C++ operator= as assignment *) match (* treat unknown calls to C++ operator= as assignment *)
actuals with actuals with
| [(AccessPath lhs_access_path); rhs_exp] -> | [(AccessExpression lhs_access_expr); rhs_exp] ->
exec_write lhs_access_path rhs_exp access_tree exec_write (AccessExpression.to_access_path lhs_access_expr) rhs_exp access_tree
| [ (AccessPath lhs_access_path) | [(AccessExpression lhs_access_expr); rhs_exp; (HilExp.AccessExpression access_expr)]
; rhs_exp -> (
; (HilExp.AccessPath (((Var.ProgramVar pvar, _), []) as dummy_ret_access_path)) ] let dummy_ret_access_path = AccessExpression.to_access_path access_expr in
when Pvar.is_frontend_tmp pvar -> match dummy_ret_access_path with
| (Var.ProgramVar pvar, _), [] when Pvar.is_frontend_tmp pvar ->
(* the frontend translates operator=(x, y) as operator=(x, y, dummy_ret) when (* the frontend translates operator=(x, y) as operator=(x, y, dummy_ret) when
operator= returns a value type *) operator= returns a value type *)
exec_write lhs_access_path rhs_exp access_tree exec_write
(AccessExpression.to_access_path lhs_access_expr)
rhs_exp access_tree
|> exec_write dummy_ret_access_path rhs_exp |> exec_write dummy_ret_access_path rhs_exp
| _ -> | _ ->
L.internal_error "Unexpected call to operator= %a in %a" HilInstr.pp instr L.internal_error "Unexpected call to operator= %a in %a" HilInstr.pp instr
Typ.Procname.pp callee_pname ; Typ.Procname.pp callee_pname ;
access_tree ) access_tree )
| _ ->
L.internal_error "Unexpected call to operator= %a in %a" HilInstr.pp instr
Typ.Procname.pp callee_pname ;
access_tree )
| _ -> | _ ->
let model = let model =
TaintSpecification.handle_unknown_call callee_pname (Option.map ~f:snd ret_opt) TaintSpecification.handle_unknown_call callee_pname (Option.map ~f:snd ret_opt)
@ -623,11 +648,14 @@ module Make (TaintSpecification : TaintSpec.S) = struct
assigning to it. understand this pattern by pretending it's the return value *) assigning to it. understand this pattern by pretending it's the return value *)
List.last actuals List.last actuals
with with
| Some HilExp.AccessPath (((Var.ProgramVar pvar, _) as ret_base), []) | Some HilExp.AccessExpression access_expr -> (
when Pvar.is_frontend_tmp pvar -> match AccessExpression.to_access_path access_expr with
| ((Var.ProgramVar pvar, _) as ret_base), [] when Pvar.is_frontend_tmp pvar ->
Some ret_base Some ret_base
| _ -> | _ ->
None ) None )
| _ ->
None )
| _ -> | _ ->
ret_opt ret_opt
in in

Loading…
Cancel
Save