You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
14 KiB
419 lines
14 KiB
(*
|
|
* 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 F = Format
|
|
|
|
let is_synchronized_library_call =
|
|
let targets = ["java.lang.StringBuffer"; "java.util.Hashtable"; "java.util.Vector"] in
|
|
fun tenv pn ->
|
|
(not (Procname.is_constructor pn))
|
|
&&
|
|
match pn with
|
|
| Procname.Java java_pname ->
|
|
let classname = Procname.Java.get_class_type_name java_pname in
|
|
List.exists targets ~f:(PatternMatch.is_subtype_of_str tenv classname)
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let should_skip_analysis =
|
|
let matcher = MethodMatcher.of_json Config.starvation_skip_analysis in
|
|
fun tenv pname actuals ->
|
|
match pname with
|
|
| Procname.Java java_pname
|
|
when Procname.Java.is_static java_pname
|
|
&& String.equal "getInstance" (Procname.get_method pname) ->
|
|
true
|
|
| _ ->
|
|
matcher tenv pname actuals
|
|
|
|
|
|
(** magical value from https://developer.android.com/topic/performance/vitals/anr *)
|
|
let android_anr_time_limit = 5.0
|
|
|
|
(* get time unit in seconds *)
|
|
let secs_of_timeunit =
|
|
let time_units =
|
|
String.Map.of_alist_exn
|
|
[ ("NANOSECONDS", 0.000_000_001)
|
|
; ("MICROSECONDS", 0.000_001)
|
|
; ("MILLISECONDS", 0.001)
|
|
; ("SECONDS", 1.0)
|
|
; ("MINUTES", 60.0)
|
|
; ("HOURS", 3_600.0)
|
|
; ("DAYS", 86_400.0) ]
|
|
in
|
|
let str_of_access_path = function
|
|
| _, [AccessPath.FieldAccess field]
|
|
when String.equal "java.util.concurrent.TimeUnit"
|
|
(Typ.Name.name (Fieldname.get_class_name field)) ->
|
|
Some (Fieldname.get_field_name field)
|
|
| _ ->
|
|
None
|
|
in
|
|
let str_of_exp = function
|
|
| HilExp.AccessExpression timeunit_acc_exp ->
|
|
HilExp.AccessExpression.to_access_path timeunit_acc_exp |> str_of_access_path
|
|
| _ ->
|
|
None
|
|
in
|
|
fun timeunit_exp -> str_of_exp timeunit_exp |> Option.bind ~f:(String.Map.find time_units)
|
|
|
|
|
|
let float_of_const_int = function
|
|
| HilExp.Constant (Const.Cint duration_lit) ->
|
|
Some (IntLit.to_float duration_lit)
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let is_excessive_secs duration = Float.(duration > android_anr_time_limit)
|
|
|
|
type severity = Low | Medium | High [@@deriving compare]
|
|
|
|
let pp_severity fmt sev =
|
|
let msg = match sev with Low -> "Low" | Medium -> "Medium" | High -> "High" in
|
|
F.pp_print_string fmt msg
|
|
|
|
|
|
let no_args_or_excessive_timeout_and_timeunit = function
|
|
| [_] ->
|
|
(* no arguments, unconditionally blocks *)
|
|
true
|
|
| [_; timeout; timeunit] ->
|
|
(* two arguments, a timeout and a time unit *)
|
|
Option.both (float_of_const_int timeout) (secs_of_timeunit timeunit)
|
|
|> Option.map ~f:(fun (duration, timeunit_secs) -> duration *. timeunit_secs)
|
|
|> Option.exists ~f:is_excessive_secs
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let no_args_or_excessive_millis_and_nanos = function
|
|
| [_] ->
|
|
(* this is a wait without timeout, it can block indefinitely *)
|
|
true
|
|
| [_; snd_arg] ->
|
|
(* 2nd argument is a duration in milliseconds *)
|
|
float_of_const_int snd_arg
|
|
|> Option.exists ~f:(fun duration -> is_excessive_secs (0.001 *. duration))
|
|
| [_; snd_arg; third_arg] ->
|
|
(* 2nd argument is a duration in milliseconds, 3rd is extra duration in nanoseconds. *)
|
|
Option.both (float_of_const_int snd_arg) (float_of_const_int third_arg)
|
|
|> Option.map ~f:(fun (duration, extra) -> 0.001 *. (duration +. (0.000_001 *. extra)))
|
|
|> Option.exists ~f:is_excessive_secs
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let standard_matchers =
|
|
let open MethodMatcher in
|
|
let high_sev =
|
|
[ {default with classname= "java.lang.Thread"; methods= ["sleep"]}
|
|
; { default with
|
|
classname= "java.lang.Thread"
|
|
; methods= ["join"]
|
|
; actuals_pred= no_args_or_excessive_millis_and_nanos }
|
|
; { default with
|
|
classname= "java.util.concurrent.CountDownLatch"
|
|
; methods= ["await"]
|
|
; actuals_pred= no_args_or_excessive_timeout_and_timeunit }
|
|
(* an IBinder.transact call is an RPC. If the 4th argument (5th counting `this` as the first)
|
|
is int-zero then a reply is expected and returned from the remote process, thus potentially
|
|
blocking. If the 4th argument is anything else, we assume a one-way call which doesn't block. *)
|
|
; { default with
|
|
classname= "android.os.IBinder"
|
|
; methods= ["transact"]
|
|
; actuals_pred= (fun actuals -> List.nth actuals 4 |> Option.exists ~f:HilExp.is_int_zero) }
|
|
]
|
|
in
|
|
let low_sev =
|
|
[ { default with
|
|
classname= "android.os.AsyncTask"
|
|
; methods= ["get"]
|
|
; actuals_pred= no_args_or_excessive_timeout_and_timeunit } ]
|
|
in
|
|
let high_sev_matcher = List.map high_sev ~f:of_record |> of_list in
|
|
let low_sev_matcher = List.map low_sev ~f:of_record |> of_list in
|
|
[(high_sev_matcher, High); (low_sev_matcher, Low)]
|
|
|
|
|
|
let is_future_get =
|
|
let open MethodMatcher in
|
|
of_record
|
|
{ default with
|
|
classname= "java.util.concurrent.Future"
|
|
; methods= ["get"]
|
|
; actuals_pred= no_args_or_excessive_timeout_and_timeunit }
|
|
|
|
|
|
let is_future_is_done =
|
|
MethodMatcher.(
|
|
of_record {default with classname= "java.util.concurrent.Future"; methods= ["isDone"]})
|
|
|
|
|
|
(* sort from High to Low *)
|
|
let may_block tenv pn actuals =
|
|
List.find_map standard_matchers ~f:(fun (matcher, sev) ->
|
|
Option.some_if (matcher tenv pn actuals) sev )
|
|
|
|
|
|
let is_monitor_wait =
|
|
MethodMatcher.(
|
|
of_record
|
|
{ default with
|
|
classname= "java.lang.Object"
|
|
; methods= ["wait"]
|
|
; actuals_pred= no_args_or_excessive_millis_and_nanos })
|
|
|
|
|
|
(* selection is a bit arbitrary as some would be generated anyway if not here; no harm though *)
|
|
(* some, like [file], need manual addition due to our lack of handling dynamic dispatch *)
|
|
let strict_mode_matcher =
|
|
let open MethodMatcher in
|
|
(* NB [default] searches superclasses too. Most of the classes below are final and we don't
|
|
really want to search superclasses for those that aren't, so for performance, disable that *)
|
|
let dont_search_superclasses = {default with search_superclasses= false} in
|
|
let matcher_records =
|
|
[ { dont_search_superclasses with
|
|
classname= "dalvik.system.BlockGuard$Policy"
|
|
; methods= ["on"]
|
|
; method_prefix= true }
|
|
; { dont_search_superclasses with
|
|
classname= "java.lang.System"
|
|
; methods= ["gc"; "runFinalization"] }
|
|
; {dont_search_superclasses with classname= "java.lang.Runtime"; methods= ["gc"]}
|
|
; {dont_search_superclasses with classname= "java.net.Socket"; methods= ["connect"]}
|
|
(* all public constructors of Socket with two or more arguments call connect *)
|
|
; { dont_search_superclasses with
|
|
classname= "java.net.Socket"
|
|
; methods= [Procname.Java.constructor_method_name]
|
|
; actuals_pred= (function [] | [_] -> false | _ -> true) }
|
|
; {dont_search_superclasses with classname= "java.net.DatagramSocket"; methods= ["connect"]}
|
|
; { dont_search_superclasses with
|
|
classname= "java.io.File"
|
|
; methods=
|
|
[ "canRead"
|
|
; "canWrite"
|
|
; "createNewFile"
|
|
; "createTempFile"
|
|
; "delete"
|
|
; "getFreeSpace"
|
|
; "getTotalSpace"
|
|
; "getUsableSpace"
|
|
; "lastModified"
|
|
; "list"
|
|
; "listFiles"
|
|
; "mkdir"
|
|
; "renameTo"
|
|
; "setExecutable"
|
|
; "setLastModified"
|
|
; "setReadable"
|
|
; "setReadOnly"
|
|
; "setWritable" ] } ]
|
|
in
|
|
of_records matcher_records
|
|
|
|
|
|
let is_strict_mode_violation tenv pn actuals =
|
|
Config.starvation_strict_mode && strict_mode_matcher tenv pn actuals
|
|
|
|
|
|
let is_annotated_nonblocking tenv pname =
|
|
ConcurrencyModels.find_override_or_superclass_annotated Annotations.ia_is_nonblocking tenv pname
|
|
|> Option.is_some
|
|
|
|
|
|
let is_annotated_lockless tenv pname =
|
|
let check annot = Annotations.(ia_ends_with annot lockless) in
|
|
ConcurrencyModels.find_override_or_superclass_annotated check tenv pname |> Option.is_some
|
|
|
|
|
|
let executor_type_str = "java.util.concurrent.Executor"
|
|
|
|
let schedules_work =
|
|
let open MethodMatcher in
|
|
(* Some methods below belong to subclasses of [Executor]; we do check for an [Executor] base class. *)
|
|
let matcher =
|
|
[ { default with
|
|
classname= executor_type_str
|
|
; methods= ["execute"; "schedule"; "scheduleAtFixedRate"; "scheduleWithFixedDelay"; "submit"]
|
|
}
|
|
; { default with
|
|
classname= "android.os.Handler"
|
|
; methods= ["post"; "postAtFrontOfQueue"; "postAtTime"; "postDelayed"] } ]
|
|
|> of_records
|
|
in
|
|
fun tenv pname -> matcher tenv pname []
|
|
|
|
|
|
let schedules_first_arg_on_ui_thread =
|
|
let open MethodMatcher in
|
|
let matcher =
|
|
{ default with
|
|
classname= "java.lang.Object"
|
|
; methods=
|
|
[ "postOnUiThread"
|
|
; "postOnUiThreadDelayed"
|
|
; "postToUiThread"
|
|
; "runOnUiThread"
|
|
; "runOnUiThreadAsync"
|
|
; "runOnUiThreadAsyncWithDelay" ] }
|
|
|> of_record
|
|
in
|
|
fun tenv pname -> matcher tenv pname []
|
|
|
|
|
|
let schedules_second_arg_on_ui_thread =
|
|
let open MethodMatcher in
|
|
let matcher =
|
|
{ default with
|
|
classname= "android.view.View"
|
|
; methods= ["post"; "postDelayed"; "postOnAnimation"] }
|
|
|> of_record
|
|
in
|
|
fun tenv pname -> matcher tenv pname []
|
|
|
|
|
|
let schedules_first_arg_on_bg_thread =
|
|
let open MethodMatcher in
|
|
let matcher =
|
|
[ {default with classname= "java.lang.Object"; methods= ["scheduleGuaranteedDelayed"]}
|
|
; {default with classname= "java.lang.Thread"; methods= ["start"]} ]
|
|
|> of_records
|
|
in
|
|
fun tenv pname -> matcher tenv pname []
|
|
|
|
|
|
type scheduler_thread_constraint = ForUIThread | ForNonUIThread | ForUnknownThread
|
|
[@@deriving equal]
|
|
|
|
(* Executors are sometimes stored in fields and annotated according to what type of thread
|
|
they schedule work on. Given an expression representing such a field, try to find the kind of
|
|
annotation constraint, if any. *)
|
|
let rec get_executor_thread_annotation_constraint tenv (receiver : HilExp.AccessExpression.t) =
|
|
match receiver with
|
|
| FieldOffset (_, field_name) when Fieldname.is_java field_name ->
|
|
Fieldname.get_class_name field_name
|
|
|> Tenv.lookup tenv
|
|
|> Option.map ~f:(fun (tstruct : Struct.t) -> tstruct.fields @ tstruct.statics)
|
|
|> Option.bind ~f:(List.find ~f:(fun (fld, _, _) -> Fieldname.equal fld field_name))
|
|
|> Option.bind ~f:(fun (_, _, annot) ->
|
|
if Annotations.(ia_ends_with annot for_ui_thread) then Some ForUIThread
|
|
else if Annotations.(ia_ends_with annot for_non_ui_thread) then Some ForNonUIThread
|
|
else None )
|
|
| Dereference prefix ->
|
|
get_executor_thread_annotation_constraint tenv prefix
|
|
| _ ->
|
|
None
|
|
|
|
|
|
(* Given an object, find the [run] method in its class and return the procname, if any *)
|
|
let get_run_method_from_runnable tenv runnable =
|
|
let run_like_methods = ["run"; "call"] in
|
|
let is_run_method = function
|
|
| Procname.Java pname when Procname.Java.(not (is_static pname)) ->
|
|
(* confusingly, the parameter list in (non-static?) Java procnames does not contain [this] *)
|
|
Procname.Java.(
|
|
List.is_empty (get_parameters pname)
|
|
&&
|
|
let methodname = get_method pname in
|
|
List.exists run_like_methods ~f:(String.equal methodname))
|
|
| _ ->
|
|
false
|
|
in
|
|
HilExp.AccessExpression.get_typ runnable tenv
|
|
|> Option.map ~f:(function Typ.{desc= Tptr (typ, _)} -> typ | typ -> typ)
|
|
|> Option.bind ~f:Typ.name
|
|
|> Option.bind ~f:(Tenv.lookup tenv)
|
|
|> Option.map ~f:(fun (tstruct : Struct.t) -> tstruct.methods)
|
|
|> Option.bind ~f:(List.find ~f:is_run_method)
|
|
|
|
|
|
(* Syntactically match for certain methods known to return executors. *)
|
|
let get_returned_executor tenv callee actuals =
|
|
let type_check =
|
|
lazy
|
|
( AnalysisCallbacks.proc_resolve_attributes callee
|
|
|> Option.exists ~f:(fun (attrs : ProcAttributes.t) ->
|
|
match attrs.ret_type.Typ.desc with
|
|
| Tstruct tname | Typ.Tptr ({desc= Tstruct tname}, _) ->
|
|
PatternMatch.is_subtype_of_str tenv tname executor_type_str
|
|
| _ ->
|
|
false ) )
|
|
in
|
|
match (callee, actuals) with
|
|
| Procname.Java java_pname, [] -> (
|
|
match Procname.Java.get_method java_pname with
|
|
| ("getForegroundExecutor" | "getBackgroundExecutor") when Lazy.force type_check ->
|
|
Some ForNonUIThread
|
|
| "getUiThreadExecutorService" when Lazy.force type_check ->
|
|
Some ForUIThread
|
|
| _ ->
|
|
None )
|
|
| _ ->
|
|
None
|
|
|
|
|
|
let is_getMainLooper =
|
|
let open MethodMatcher in
|
|
of_record
|
|
{ default with
|
|
classname= "android.os.Looper"
|
|
; methods= ["getMainLooper"]
|
|
; actuals_pred= List.is_empty }
|
|
|
|
|
|
let is_handler_constructor =
|
|
let open MethodMatcher in
|
|
of_record
|
|
{ default with
|
|
classname= "android.os.Handler"
|
|
; methods= [Procname.Java.constructor_method_name]
|
|
; actuals_pred= (fun actuals -> not (List.is_empty actuals)) }
|
|
|
|
|
|
let is_thread_constructor =
|
|
let open MethodMatcher in
|
|
of_record
|
|
{ default with
|
|
classname= "java.lang.Thread"
|
|
; search_superclasses= false
|
|
; methods= [Procname.Java.constructor_method_name] }
|
|
|
|
|
|
let is_assume_true =
|
|
let open MethodMatcher in
|
|
of_records
|
|
[ { default with
|
|
classname= "com.facebook.common.internal.Preconditions"
|
|
; methods= ["checkArgument"; "checkState"] }
|
|
; { default with
|
|
classname= "com.facebook.infer.annotation.Assertions"
|
|
; methods= ["assertCondition"; "assumeCondition"] }
|
|
; { default with
|
|
classname= "com.google.common.base.Preconditions"
|
|
; methods= ["checkArgument"; "checkState"] } ]
|
|
|
|
|
|
let is_java_main_method (pname : Procname.t) =
|
|
let pointer_to_array_of_java_lang_string =
|
|
Typ.(mk_ptr (mk_array StdTyp.Java.pointer_to_java_lang_string))
|
|
in
|
|
let check_main_args args =
|
|
match args with [arg] -> Typ.equal pointer_to_array_of_java_lang_string arg | _ -> false
|
|
in
|
|
match pname with
|
|
| C _ | Linters_dummy_method | Block _ | ObjC_Cpp _ | WithBlockParameters _ ->
|
|
false
|
|
| Java java_pname ->
|
|
Procname.Java.is_static java_pname
|
|
&& String.equal "main" (Procname.get_method pname)
|
|
&& Typ.equal StdTyp.void (Procname.Java.get_return_typ java_pname)
|
|
&& check_main_args (Procname.Java.get_parameters java_pname)
|