Summary: As there are no dependencies between procedure and file analyses in RacerD, split them into separate modules. Reviewed By: ezgicicek Differential Revision: D26198874 fbshipit-source-id: 032aad9d8master
							parent
							
								
									bc49f1deb1
								
							
						
					
					
						commit
						8b32da127e
					
				| @ -0,0 +1,345 @@ | |||||||
|  | (* | ||||||
|  |  * Copyright (c) Facebook, Inc. and its affiliates. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the MIT license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  *) | ||||||
|  | 
 | ||||||
|  | open! IStd | ||||||
|  | module AccessExpression = HilExp.AccessExpression | ||||||
|  | module F = Format | ||||||
|  | module L = Logging | ||||||
|  | 
 | ||||||
|  | type analysis_data = | ||||||
|  |   {interproc: RacerDDomain.summary InterproceduralAnalysis.t; formals: FormalMap.t} | ||||||
|  | 
 | ||||||
|  | module TransferFunctions (CFG : ProcCfg.S) = struct | ||||||
|  |   module CFG = CFG | ||||||
|  |   module Domain = RacerDDomain | ||||||
|  | 
 | ||||||
|  |   type nonrec analysis_data = analysis_data | ||||||
|  | 
 | ||||||
|  |   let do_call_acquiring_ownership ret_access_exp astate = | ||||||
|  |     let open Domain in | ||||||
|  |     let ownership = | ||||||
|  |       OwnershipDomain.add ret_access_exp OwnershipAbstractValue.owned astate.ownership | ||||||
|  |     in | ||||||
|  |     {astate with ownership} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_call_without_summary tenv ret_access_exp callee_pname actuals astate = | ||||||
|  |     let open Domain in | ||||||
|  |     if RacerDModels.is_synchronized_container_constructor tenv callee_pname actuals then | ||||||
|  |       apply_to_first_actual actuals astate ~f:(fun receiver -> | ||||||
|  |           let attribute_map = AttributeMapDomain.add receiver Synchronized astate.attribute_map in | ||||||
|  |           {astate with attribute_map} ) | ||||||
|  |     else if RacerDModels.is_converter_to_synchronized_container tenv callee_pname actuals then | ||||||
|  |       let attribute_map = AttributeMapDomain.add ret_access_exp Synchronized astate.attribute_map in | ||||||
|  |       {astate with attribute_map} | ||||||
|  |     else if RacerDModels.is_box callee_pname then | ||||||
|  |       apply_to_first_actual actuals astate ~f:(fun actual_access_expr -> | ||||||
|  |           if AttributeMapDomain.is_functional astate.attribute_map actual_access_expr then | ||||||
|  |             (* TODO: check for constants, which are functional? *) | ||||||
|  |             let attribute_map = | ||||||
|  |               AttributeMapDomain.add ret_access_exp Functional astate.attribute_map | ||||||
|  |             in | ||||||
|  |             {astate with attribute_map} | ||||||
|  |           else astate ) | ||||||
|  |     else do_call_acquiring_ownership ret_access_exp astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_for_unannotated_interface_call tenv formals call_flags callee_pname actuals loc astate | ||||||
|  |       = | ||||||
|  |     if RacerDModels.should_flag_interface_call tenv actuals call_flags callee_pname then | ||||||
|  |       Domain.add_unannotated_call_access formals callee_pname actuals loc astate | ||||||
|  |     else astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_for_thread_assert_effect ret_access_exp callee_pname (astate : Domain.t) = | ||||||
|  |     let open Domain in | ||||||
|  |     match ConcurrencyModels.get_thread_assert_effect callee_pname with | ||||||
|  |     | BackgroundThread -> | ||||||
|  |         {astate with threads= ThreadsDomain.AnyThread} | ||||||
|  |     | MainThread -> | ||||||
|  |         {astate with threads= ThreadsDomain.AnyThreadButSelf} | ||||||
|  |     | MainThreadIfTrue -> | ||||||
|  |         let attribute_map = | ||||||
|  |           AttributeMapDomain.add ret_access_exp Attribute.OnMainThread astate.attribute_map | ||||||
|  |         in | ||||||
|  |         {astate with attribute_map} | ||||||
|  |     | UnknownThread -> | ||||||
|  |         astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_call_summary analyze_dependency tenv formals ret_access_exp callee_pname actuals loc | ||||||
|  |       astate = | ||||||
|  |     match analyze_dependency callee_pname with | ||||||
|  |     | Some (callee_proc_desc, summary) -> | ||||||
|  |         Domain.integrate_summary formals ~callee_proc_desc summary ret_access_exp callee_pname | ||||||
|  |           actuals loc astate | ||||||
|  |     | None -> | ||||||
|  |         process_call_without_summary tenv ret_access_exp callee_pname actuals astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_lock_effect_or_summary analyze_dependency tenv formals ret_access_exp callee_pname | ||||||
|  |       actuals loc (astate : Domain.t) = | ||||||
|  |     let open Domain in | ||||||
|  |     match ConcurrencyModels.get_lock_effect callee_pname actuals with | ||||||
|  |     | Lock _ | GuardLock _ | GuardConstruct {acquire_now= true} -> | ||||||
|  |         { astate with | ||||||
|  |           locks= LockDomain.acquire_lock astate.locks | ||||||
|  |         ; threads= ThreadsDomain.update_for_lock_use astate.threads } | ||||||
|  |     | Unlock _ | GuardDestroy _ | GuardUnlock _ -> | ||||||
|  |         { astate with | ||||||
|  |           locks= LockDomain.release_lock astate.locks | ||||||
|  |         ; threads= ThreadsDomain.update_for_lock_use astate.threads } | ||||||
|  |     | LockedIfTrue _ | GuardLockedIfTrue _ -> | ||||||
|  |         let attribute_map = | ||||||
|  |           AttributeMapDomain.add ret_access_exp Attribute.LockHeld astate.attribute_map | ||||||
|  |         in | ||||||
|  |         {astate with attribute_map; threads= ThreadsDomain.update_for_lock_use astate.threads} | ||||||
|  |     | GuardConstruct {acquire_now= false} -> | ||||||
|  |         astate | ||||||
|  |     | NoEffect -> | ||||||
|  |         process_call_summary analyze_dependency tenv formals ret_access_exp callee_pname actuals loc | ||||||
|  |           astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_for_functional_values tenv ret_access_exp callee_pname (astate : Domain.t) = | ||||||
|  |     let open Domain in | ||||||
|  |     if PatternMatch.override_exists RacerDModels.is_functional tenv callee_pname then | ||||||
|  |       let attribute_map = AttributeMapDomain.add ret_access_exp Functional astate.attribute_map in | ||||||
|  |       {astate with attribute_map} | ||||||
|  |     else astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let process_for_onwership_acquisition tenv ret_access_exp callee_pname astate = | ||||||
|  |     if | ||||||
|  |       PatternMatch.override_exists | ||||||
|  |         (RacerDModels.has_return_annot Annotations.ia_is_returns_ownership) | ||||||
|  |         tenv callee_pname | ||||||
|  |     then do_call_acquiring_ownership ret_access_exp astate | ||||||
|  |     else astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let do_proc_call ret_base callee_pname actuals call_flags loc | ||||||
|  |       {interproc= {tenv; analyze_dependency}; formals} (astate : Domain.t) = | ||||||
|  |     let ret_access_exp = AccessExpression.base ret_base in | ||||||
|  |     process_for_unannotated_interface_call tenv formals call_flags callee_pname actuals loc astate | ||||||
|  |     |> process_for_thread_assert_effect ret_access_exp callee_pname | ||||||
|  |     |> process_lock_effect_or_summary analyze_dependency tenv formals ret_access_exp callee_pname | ||||||
|  |          actuals loc | ||||||
|  |     |> process_for_functional_values tenv ret_access_exp callee_pname | ||||||
|  |     |> process_for_onwership_acquisition tenv ret_access_exp callee_pname | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let do_assignment lhs_access_exp rhs_exp loc {interproc= {tenv}; formals} (astate : Domain.t) = | ||||||
|  |     let open Domain in | ||||||
|  |     let astate = add_access tenv formals loc ~is_write:false astate rhs_exp in | ||||||
|  |     let rhs_access_exprs = HilExp.get_access_exprs rhs_exp in | ||||||
|  |     let is_functional = | ||||||
|  |       (not (List.is_empty rhs_access_exprs)) | ||||||
|  |       && List.for_all rhs_access_exprs ~f:(AttributeMapDomain.is_functional astate.attribute_map) | ||||||
|  |       && | ||||||
|  |       match AccessExpression.get_typ lhs_access_exp tenv with | ||||||
|  |       | Some {Typ.desc= Typ.Tint ILong | Tfloat FDouble} -> | ||||||
|  |           (* writes to longs and doubles are not guaranteed to be atomic in Java | ||||||
|  |              (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7), so there | ||||||
|  |              can be a race even if the RHS is functional *) | ||||||
|  |           false | ||||||
|  |       | _ -> | ||||||
|  |           true | ||||||
|  |     in | ||||||
|  |     let astate = | ||||||
|  |       if is_functional then | ||||||
|  |         (* we want to forget about writes to @Functional fields altogether, otherwise we'll | ||||||
|  |            report spurious read/write races *) | ||||||
|  |         astate | ||||||
|  |       else | ||||||
|  |         add_access tenv formals loc ~is_write:true astate (HilExp.AccessExpression lhs_access_exp) | ||||||
|  |     in | ||||||
|  |     let ownership = OwnershipDomain.propagate_assignment lhs_access_exp rhs_exp astate.ownership in | ||||||
|  |     let attribute_map = | ||||||
|  |       AttributeMapDomain.propagate_assignment lhs_access_exp rhs_exp astate.attribute_map | ||||||
|  |     in | ||||||
|  |     {astate with ownership; attribute_map} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let do_assume formals assume_exp loc tenv (astate : Domain.t) = | ||||||
|  |     let open Domain in | ||||||
|  |     let apply_choice bool_value (acc : Domain.t) = function | ||||||
|  |       | Attribute.LockHeld -> | ||||||
|  |           let locks = | ||||||
|  |             if bool_value then LockDomain.acquire_lock acc.locks | ||||||
|  |             else LockDomain.release_lock acc.locks | ||||||
|  |           in | ||||||
|  |           {acc with locks} | ||||||
|  |       | Attribute.OnMainThread -> | ||||||
|  |           let threads = | ||||||
|  |             if bool_value then ThreadsDomain.AnyThreadButSelf else ThreadsDomain.AnyThread | ||||||
|  |           in | ||||||
|  |           {acc with threads} | ||||||
|  |       | Attribute.(Functional | Nothing | Synchronized) -> | ||||||
|  |           acc | ||||||
|  |     in | ||||||
|  |     let astate = add_access tenv formals loc ~is_write:false astate assume_exp in | ||||||
|  |     match HilExp.get_access_exprs assume_exp with | ||||||
|  |     | [access_expr] -> | ||||||
|  |         HilExp.eval_boolean_exp access_expr assume_exp | ||||||
|  |         |> Option.value_map ~default:astate ~f:(fun bool_value -> | ||||||
|  |                (* prune (prune_exp) can only evaluate to true if the choice is [bool_value]. | ||||||
|  |                   add the constraint that the choice must be [bool_value] to the state *) | ||||||
|  |                AttributeMapDomain.get access_expr astate.attribute_map | ||||||
|  |                |> apply_choice bool_value astate ) | ||||||
|  |     | _ -> | ||||||
|  |         astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let exec_instr astate ({interproc= {proc_desc; tenv}; formals} as analysis_data) _ instr = | ||||||
|  |     match (instr : HilInstr.t) with | ||||||
|  |     | Call (ret_base, Direct callee_pname, actuals, call_flags, loc) -> | ||||||
|  |         let astate = Domain.add_reads_of_hilexps tenv formals actuals loc astate in | ||||||
|  |         if RacerDModels.acquires_ownership callee_pname tenv then | ||||||
|  |           do_call_acquiring_ownership (AccessExpression.base ret_base) astate | ||||||
|  |         else if RacerDModels.is_container_write tenv callee_pname then | ||||||
|  |           Domain.add_container_access tenv formals ~is_write:true ret_base callee_pname actuals loc | ||||||
|  |             astate | ||||||
|  |         else if RacerDModels.is_container_read tenv callee_pname then | ||||||
|  |           Domain.add_container_access tenv formals ~is_write:false ret_base callee_pname actuals loc | ||||||
|  |             astate | ||||||
|  |         else do_proc_call ret_base callee_pname actuals call_flags loc analysis_data astate | ||||||
|  |     | Call (_, Indirect _, _, _, _) -> | ||||||
|  |         if Procname.is_java (Procdesc.get_proc_name proc_desc) then | ||||||
|  |           L.(die InternalError) "Unexpected indirect call instruction %a" HilInstr.pp instr | ||||||
|  |         else astate | ||||||
|  |     | Assign (lhs_access_expr, rhs_exp, loc) -> | ||||||
|  |         do_assignment lhs_access_expr rhs_exp loc analysis_data astate | ||||||
|  |     | Assume (assume_exp, _, _, loc) -> | ||||||
|  |         do_assume formals assume_exp loc tenv astate | ||||||
|  |     | Metadata _ -> | ||||||
|  |         astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let pp_session_name _node fmt = F.pp_print_string fmt "racerd" | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions (ProcCfg.Normal)) | ||||||
|  | 
 | ||||||
|  | (** Compute the attributes (of static variables) set up by the class initializer. *) | ||||||
|  | let set_class_init_attributes interproc (astate : RacerDDomain.t) = | ||||||
|  |   let open RacerDDomain in | ||||||
|  |   let attribute_map = | ||||||
|  |     ConcurrencyUtils.get_java_class_initializer_summary_of interproc | ||||||
|  |     |> Option.value_map ~default:AttributeMapDomain.top ~f:(fun summary -> summary.attributes) | ||||||
|  |   in | ||||||
|  |   ({astate with attribute_map} : t) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | (** Compute the attributes of instance variables that all constructors agree on. *) | ||||||
|  | let set_constructor_attributes ({InterproceduralAnalysis.proc_desc} as interproc) | ||||||
|  |     (astate : RacerDDomain.t) = | ||||||
|  |   let open RacerDDomain in | ||||||
|  |   let procname = Procdesc.get_proc_name proc_desc in | ||||||
|  |   (* make a local [this] variable, for replacing all constructor attribute map keys' roots *) | ||||||
|  |   let local_this = Pvar.mk Mangled.this procname |> Var.of_pvar in | ||||||
|  |   let make_local exp = | ||||||
|  |     (* contract here matches that of [StarvationDomain.summary_of_astate] *) | ||||||
|  |     let var, typ = HilExp.AccessExpression.get_base exp in | ||||||
|  |     if Var.is_global var then | ||||||
|  |       (* let expressions rooted at globals unchanged, these are probably from class initialiser *) | ||||||
|  |       exp | ||||||
|  |     else ( | ||||||
|  |       assert (Var.is_this var) ; | ||||||
|  |       HilExp.AccessExpression.replace_base ~remove_deref_after_base:false (local_this, typ) exp ) | ||||||
|  |   in | ||||||
|  |   let localize_attrs attributes = | ||||||
|  |     AttributeMapDomain.(fold (fun exp attr acc -> add (make_local exp) attr acc) attributes empty) | ||||||
|  |   in | ||||||
|  |   let attribute_map = | ||||||
|  |     ConcurrencyUtils.get_java_constructor_summaries_of interproc | ||||||
|  |     (* make instances of [this] local to the current procedure and select only the attributes *) | ||||||
|  |     |> List.map ~f:(fun (summary : summary) -> localize_attrs summary.attributes) | ||||||
|  |     (* join all the attribute maps together *) | ||||||
|  |     |> List.reduce ~f:AttributeMapDomain.join | ||||||
|  |     |> Option.value ~default:AttributeMapDomain.top | ||||||
|  |   in | ||||||
|  |   {astate with attribute_map} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | let set_initial_attributes ({InterproceduralAnalysis.proc_desc} as interproc) astate = | ||||||
|  |   let procname = Procdesc.get_proc_name proc_desc in | ||||||
|  |   match procname with | ||||||
|  |   | Procname.Java java_pname when Procname.Java.is_class_initializer java_pname -> | ||||||
|  |       (* we are analyzing the class initializer, don't go through on-demand again *) | ||||||
|  |       astate | ||||||
|  |   | Procname.Java java_pname when Procname.Java.(is_constructor java_pname || is_static java_pname) | ||||||
|  |     -> | ||||||
|  |       (* analyzing a constructor or static method, so we need the attributes established by the | ||||||
|  |          class initializer *) | ||||||
|  |       set_class_init_attributes interproc astate | ||||||
|  |   | Procname.Java _ -> | ||||||
|  |       (* we are analyzing an instance method, so we need constructor-established attributes | ||||||
|  |          which will include those by the class initializer *) | ||||||
|  |       set_constructor_attributes interproc astate | ||||||
|  |   | _ -> | ||||||
|  |       astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | let analyze ({InterproceduralAnalysis.proc_desc; tenv} as interproc) = | ||||||
|  |   let open RacerDDomain in | ||||||
|  |   let proc_name = Procdesc.get_proc_name proc_desc in | ||||||
|  |   let open ConcurrencyModels in | ||||||
|  |   let add_owned_formal acc base = OwnershipDomain.add base OwnershipAbstractValue.owned acc in | ||||||
|  |   let add_conditionally_owned_formal = | ||||||
|  |     let is_owned_formal {Annot.class_name} = | ||||||
|  |       (* [@InjectProp] allocates a fresh object to bind to the parameter *) | ||||||
|  |       String.is_suffix ~suffix:Annotations.inject_prop class_name | ||||||
|  |     in | ||||||
|  |     let method_annotation = (Procdesc.get_attributes proc_desc).method_annotation in | ||||||
|  |     let is_inject_prop = Annotations.ma_has_annotation_with method_annotation is_owned_formal in | ||||||
|  |     fun acc formal formal_index -> | ||||||
|  |       let ownership_value = | ||||||
|  |         if is_inject_prop then OwnershipAbstractValue.owned | ||||||
|  |         else OwnershipAbstractValue.make_owned_if formal_index | ||||||
|  |       in | ||||||
|  |       OwnershipDomain.add formal ownership_value acc | ||||||
|  |   in | ||||||
|  |   if RacerDModels.should_analyze_proc tenv proc_name then | ||||||
|  |     let locks = | ||||||
|  |       if Procdesc.is_java_synchronized proc_desc then LockDomain.(acquire_lock bottom) | ||||||
|  |       else LockDomain.bottom | ||||||
|  |     in | ||||||
|  |     let threads = | ||||||
|  |       if runs_on_ui_thread tenv proc_name || RacerDModels.is_thread_confined_method tenv proc_name | ||||||
|  |       then ThreadsDomain.AnyThreadButSelf | ||||||
|  |       else if | ||||||
|  |         Procdesc.is_java_synchronized proc_desc || RacerDModels.is_marked_thread_safe proc_name tenv | ||||||
|  |       then ThreadsDomain.AnyThread | ||||||
|  |       else ThreadsDomain.NoThread | ||||||
|  |     in | ||||||
|  |     let ownership = | ||||||
|  |       let is_initializer = RacerDModels.is_initializer tenv proc_name in | ||||||
|  |       let is_injected = | ||||||
|  |         is_initializer && Annotations.pdesc_has_return_annot proc_desc Annotations.ia_is_inject | ||||||
|  |       in | ||||||
|  |       Procdesc.get_formals proc_desc | ||||||
|  |       |> List.foldi ~init:OwnershipDomain.empty ~f:(fun index acc (name, typ) -> | ||||||
|  |              let base = | ||||||
|  |                AccessPath.base_of_pvar (Pvar.mk name proc_name) typ |> AccessExpression.base | ||||||
|  |              in | ||||||
|  |              if is_injected then | ||||||
|  |                (* if a constructor is called via DI, all of its formals will be freshly allocated and | ||||||
|  |                   therefore owned. we assume that constructors annotated with [@Inject] will only be | ||||||
|  |                   called via DI or using fresh parameters. *) | ||||||
|  |                add_owned_formal acc base | ||||||
|  |              else if is_initializer && Int.equal 0 index then | ||||||
|  |                (* express that the constructor owns [this] *) | ||||||
|  |                add_owned_formal acc base | ||||||
|  |              else add_conditionally_owned_formal acc base index ) | ||||||
|  |     in | ||||||
|  |     let initial = set_initial_attributes interproc {bottom with ownership; threads; locks} in | ||||||
|  |     let formals = FormalMap.make proc_desc in | ||||||
|  |     let analysis_data = {interproc; formals} in | ||||||
|  |     Analyzer.compute_post analysis_data ~initial proc_desc | ||||||
|  |     |> Option.map ~f:(astate_to_summary proc_desc formals) | ||||||
|  |   else Some empty_summary | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | (* | ||||||
|  |  * Copyright (c) Facebook, Inc. and its affiliates. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the MIT license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  *) | ||||||
|  | 
 | ||||||
|  | open! IStd | ||||||
|  | 
 | ||||||
|  | val analyze : RacerDDomain.summary InterproceduralAnalysis.t -> RacerDDomain.summary option | ||||||
					Loading…
					
					
				
		Reference in new issue