From 20a7e9d75b729d8b2a93666d583444ad5d7aa730 Mon Sep 17 00:00:00 2001 From: Nikos Gorogiannis Date: Mon, 25 Nov 2019 08:02:36 -0800 Subject: [PATCH] [starvation][whole-program] add a bit of typestate/dataflow Summary: - Unify treatment of modelled and annotated executors by making things go through attributes. - Add a return attribute to summaries, so that we can track flows of thread guards/executors/future stuff through returned values. - Dispatch modeled functions to model summaries. This will help in following diffs where runnables will also go through attributes. Reviewed By: skcho Differential Revision: D18660185 fbshipit-source-id: e26b1083e --- infer/src/IR/Typ.ml | 2 + infer/src/IR/Typ.mli | 2 + infer/src/concurrency/StarvationModels.ml | 10 +- infer/src/concurrency/StarvationModels.mli | 4 +- infer/src/concurrency/starvation.ml | 102 +++++++++-------- infer/src/concurrency/starvationDomain.ml | 69 +++++++----- infer/src/concurrency/starvationDomain.mli | 21 +++- .../AttributeFlows.java | 104 ++++++++++++++++++ .../starvation-whole-program/Executors.java | 32 ++++++ .../ModeledExecutors.java | 25 ----- .../java/starvation-whole-program/issues.exp | 14 ++- 11 files changed, 272 insertions(+), 113 deletions(-) create mode 100644 infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java create mode 100644 infer/tests/codetoanalyze/java/starvation-whole-program/Executors.java diff --git a/infer/src/IR/Typ.ml b/infer/src/IR/Typ.ml index c69558cba..6a7797e4d 100644 --- a/infer/src/IR/Typ.ml +++ b/infer/src/IR/Typ.ml @@ -1422,6 +1422,8 @@ module Fieldname = struct let equal = [%compare.equal: t] + let is_java = function Java _ -> true | Clang _ -> false + module T = struct type nonrec t = t diff --git a/infer/src/IR/Typ.mli b/infer/src/IR/Typ.mli index 1e635af36..a747a3f63 100644 --- a/infer/src/IR/Typ.mli +++ b/infer/src/IR/Typ.mli @@ -639,6 +639,8 @@ module Fieldname : sig val equal : t -> t -> bool (** Equality for field names. *) + val is_java : t -> bool + module Set : Caml.Set.S with type elt = t (** Set for fieldnames *) diff --git a/infer/src/concurrency/StarvationModels.ml b/infer/src/concurrency/StarvationModels.ml index 3f028c78b..adb5f22b3 100644 --- a/infer/src/concurrency/StarvationModels.ml +++ b/infer/src/concurrency/StarvationModels.ml @@ -235,12 +235,12 @@ let schedules_work_on_bg_thread = type executor_thread_constraint = ForUIThread | ForNonUIThread | ForUnknownThread [@@deriving equal] -(* Executors are usually stored in fields and annotated according to what type of thread +(* 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_constraint tenv (receiver : HilExp.AccessExpression.t) = +let rec get_executor_thread_annotation_constraint tenv (receiver : HilExp.AccessExpression.t) = match receiver with - | FieldOffset (_, field_name) -> + | FieldOffset (_, field_name) when Typ.Fieldname.is_java field_name -> Typ.Fieldname.Java.get_class field_name |> Typ.Name.Java.from_string |> Tenv.lookup tenv |> Option.map ~f:(fun (tstruct : Typ.Struct.t) -> tstruct.fields @ tstruct.statics) @@ -250,7 +250,7 @@ let rec get_executor_thread_constraint tenv (receiver : HilExp.AccessExpression. else if Annotations.(ia_ends_with annot for_non_ui_thread) then Some ForNonUIThread else None ) | Dereference prefix -> - get_executor_thread_constraint tenv prefix + get_executor_thread_annotation_constraint tenv prefix | _ -> None @@ -274,7 +274,7 @@ let get_run_method_from_runnable tenv runnable = (* Syntactically match for certain methods known to return executors. *) -let get_executor_effect ~attrs_of_pname tenv callee actuals = +let get_returned_executor ~attrs_of_pname tenv callee actuals = let type_check = lazy ( attrs_of_pname callee diff --git a/infer/src/concurrency/StarvationModels.mli b/infer/src/concurrency/StarvationModels.mli index f7f0d9534..8f578b846 100644 --- a/infer/src/concurrency/StarvationModels.mli +++ b/infer/src/concurrency/StarvationModels.mli @@ -39,7 +39,7 @@ val schedules_work : Tenv.t -> Typ.Procname.t -> bool type executor_thread_constraint = ForUIThread | ForNonUIThread | ForUnknownThread [@@deriving equal] -val get_executor_thread_constraint : +val get_executor_thread_annotation_constraint : Tenv.t -> HilExp.AccessExpression.t -> executor_thread_constraint option (** given an executor receiver, get its thread constraint, if any. [None] means lookup somehow failed, whereas [Some UnknownThread] means the receiver is an unannotated executor. *) @@ -47,7 +47,7 @@ val get_executor_thread_constraint : val get_run_method_from_runnable : Tenv.t -> HilExp.AccessExpression.t -> Typ.Procname.t option (** given a receiver, find the [run()] method in the appropriate class *) -val get_executor_effect : +val get_returned_executor : attrs_of_pname:(Typ.Procname.t -> ProcAttributes.t option) -> Tenv.t -> Typ.Procname.t diff --git a/infer/src/concurrency/starvation.ml b/infer/src/concurrency/starvation.ml index 967d2f3a7..ccc84b1ee 100644 --- a/infer/src/concurrency/starvation.ml +++ b/infer/src/concurrency/starvation.ml @@ -72,21 +72,16 @@ module TransferFunctions (CFG : ProcCfg.S) = struct astate - let do_thread_assert_effect (ret_var, _) callee (astate : Domain.t) = + let get_exp_attributes tenv exp (astate : Domain.t) = let open Domain in - match ConcurrencyModels.get_thread_assert_effect callee with - | BackgroundThread -> - {astate with thread= ThreadDomain.BGThread} - | MainThread -> - {astate with thread= ThreadDomain.UIThread} - | MainThreadIfTrue -> - let attributes = AttributeDomain.add ret_var Attribute.Thread astate.attributes in - {astate with attributes} - | UnknownThread -> - astate + AttributeDomain.find_opt exp astate.attributes + |> IOption.if_none_evalopt ~f:(fun () -> + StarvationModels.get_executor_thread_annotation_constraint tenv exp + |> Option.map ~f:(fun constr -> Attribute.Executor constr) ) let do_work_scheduling tenv callee actuals loc (astate : Domain.t) = + let open Domain in let schedule_work runnable init thread = StarvationModels.get_run_method_from_runnable tenv runnable |> Option.fold ~init ~f:(Domain.schedule_work loc thread) @@ -94,16 +89,14 @@ module TransferFunctions (CFG : ProcCfg.S) = struct match actuals with | [HilExp.AccessExpression executor; HilExp.AccessExpression runnable] when StarvationModels.schedules_work tenv callee -> - let thread_opt = - IList.force_until_first_some - [ (* match an executor call that we have a model for *) - lazy (StarvationModels.get_executor_thread_constraint tenv executor) - ; (* match an executor call where the executor is stored in a variable *) - lazy (Domain.AttributeDomain.get_executor_constraint executor astate.attributes) - ; (* the receiver is an executor stored in a variable for which we have no knowledge *) - lazy (Some StarvationModels.ForUnknownThread) ] + let thread = + match get_exp_attributes tenv executor astate with + | Some (Attribute.Executor constr) -> + constr + | _ -> + StarvationModels.ForUnknownThread in - Option.fold thread_opt ~init:astate ~f:(schedule_work runnable) + schedule_work runnable astate thread | HilExp.AccessExpression runnable :: _ when StarvationModels.schedules_work_on_ui_thread tenv callee -> schedule_work runnable astate StarvationModels.ForUIThread @@ -114,17 +107,43 @@ module TransferFunctions (CFG : ProcCfg.S) = struct astate - let do_executor_effect tenv (ret_var, _) callee actuals astate = + let do_assignment tenv lhs_access_exp rhs_exp (astate : Domain.t) = + HilExp.get_access_exprs rhs_exp + |> List.filter_map ~f:(fun exp -> get_exp_attributes tenv exp astate) + |> List.reduce ~f:Domain.Attribute.join + |> Option.value_map ~default:astate ~f:(fun attribute -> + let attributes = Domain.AttributeDomain.add lhs_access_exp attribute astate.attributes in + {astate with attributes} ) + + + let do_call ProcData.{tenv; summary} lhs callee actuals loc astate = let open Domain in - StarvationModels.get_executor_effect ~attrs_of_pname tenv callee actuals - |> Option.fold ~init:astate ~f:(fun acc effect -> - let attributes = - AttributeDomain.add ret_var (Attribute.Executor effect) astate.attributes - in - {acc with attributes} ) + let get_returned_executor_summary () = + StarvationModels.get_returned_executor ~attrs_of_pname tenv callee actuals + |> Option.map ~f:(fun thread_constraint -> + {empty_summary with return_attribute= Attribute.Executor thread_constraint} ) + in + let get_thread_assert_summary () = + match ConcurrencyModels.get_thread_assert_effect callee with + | BackgroundThread -> + Some {empty_summary with thread= ThreadDomain.BGThread} + | MainThread -> + Some {empty_summary with thread= ThreadDomain.UIThread} + | MainThreadIfTrue -> + Some {empty_summary with return_attribute= Attribute.ThreadGuard} + | UnknownThread -> + None + in + let get_callee_summary () = Payload.read ~caller_summary:summary ~callee_pname:callee in + let callsite = CallSite.make callee loc in + get_returned_executor_summary () + |> IOption.if_none_evalopt ~f:get_thread_assert_summary + |> IOption.if_none_evalopt ~f:get_callee_summary + |> Option.fold ~init:astate ~f:(Domain.integrate_summary ~tenv ~lhs callsite) - let exec_instr (astate : Domain.t) {ProcData.summary; tenv; extras} _ (instr : HilInstr.t) = + let exec_instr (astate : Domain.t) ({ProcData.summary; tenv; extras} as procdata) _ + (instr : HilInstr.t) = let open ConcurrencyModels in let open StarvationModels in let get_lock_path = get_lock_path extras in @@ -134,20 +153,17 @@ module TransferFunctions (CFG : ProcCfg.S) = struct List.filter_map ~f:get_lock_path locks |> Domain.acquire ~tenv astate ~procname ~loc in let do_unlock locks astate = List.filter_map ~f:get_lock_path locks |> Domain.release astate in - let do_call callee loc astate = - let callsite = CallSite.make callee loc in - Payload.read ~caller_summary:summary ~callee_pname:callee - |> Option.fold ~init:astate ~f:(Domain.integrate_summary ~tenv callsite) - in match instr with - | Assign _ | Call (_, Indirect _, _, _, _) -> - astate + | Assign (lhs_access_exp, rhs_exp, _) -> + do_assignment tenv lhs_access_exp rhs_exp astate | Metadata (Sil.ExitScope (vars, _)) -> {astate with attributes= Domain.AttributeDomain.exit_scope vars astate.attributes} | Metadata _ -> astate | Assume (assume_exp, _, _, _) -> do_assume assume_exp astate + | Call (_, Indirect _, _, _, _) -> + astate | Call (_, Direct callee, actuals, _, _) when should_skip_analysis tenv callee actuals -> astate | Call (ret_base, Direct callee, actuals, _, loc) -> ( @@ -178,19 +194,17 @@ module TransferFunctions (CFG : ProcCfg.S) = struct | NoEffect when is_java && is_strict_mode_violation tenv callee actuals -> Domain.strict_mode_call ~callee ~loc astate | NoEffect when is_java -> ( - let astate = - do_thread_assert_effect ret_base callee astate - |> do_executor_effect tenv ret_base callee actuals - |> do_work_scheduling tenv callee actuals loc - in + let ret_exp = HilExp.AccessExpression.base ret_base in + let astate = do_work_scheduling tenv callee actuals loc astate in match may_block tenv callee actuals with | Some sev -> Domain.blocking_call ~callee sev ~loc astate | None -> - do_call callee loc astate ) + do_call procdata ret_exp callee actuals loc astate ) | NoEffect -> (* in C++/Obj C we only care about deadlocks, not starvation errors *) - do_call callee loc astate ) + let ret_exp = HilExp.AccessExpression.base ret_base in + do_call procdata ret_exp callee actuals loc astate ) let pp_session_name _node fmt = F.pp_print_string fmt "starvation" @@ -241,7 +255,7 @@ let analyze_procedure {Callbacks.exe_env; summary} = in Analyzer.compute_post proc_data ~initial |> Option.map ~f:filter_blocks - |> Option.map ~f:Domain.summary_of_astate + |> Option.map ~f:(Domain.summary_of_astate proc_desc) |> Option.fold ~init:summary ~f:(fun acc payload -> Payload.update_summary payload acc) @@ -580,7 +594,7 @@ let get_summary_of_scheduled_work (work_item : Domain.ScheduledWorkItem.t) = let callsite = CallSite.make work_item.procname work_item.loc in Summary.OnDisk.get work_item.procname |> Option.bind ~f:(fun (summary : Summary.t) -> Payloads.starvation summary.payloads) - |> Option.map ~f:(Domain.integrate_summary ?tenv:None callsite astate) + |> Option.map ~f:(Domain.integrate_summary callsite astate) |> Option.map ~f:(fun (astate : Domain.t) -> astate.critical_pairs) diff --git a/infer/src/concurrency/starvationDomain.ml b/infer/src/concurrency/starvationDomain.ml index 10016b1ea..25232ea61 100644 --- a/infer/src/concurrency/starvationDomain.ml +++ b/infer/src/concurrency/starvationDomain.ml @@ -444,7 +444,7 @@ module GuardToLockMap = struct end module Attribute = struct - type t = Nothing | Thread | Executor of StarvationModels.executor_thread_constraint + type t = Nothing | ThreadGuard | Executor of StarvationModels.executor_thread_constraint [@@deriving equal] let top = Nothing @@ -455,8 +455,8 @@ module Attribute = struct ( match t with | Nothing -> "Nothing" - | Thread -> - "Thread" + | ThreadGuard -> + "ThreadGuard" | Executor StarvationModels.ForUIThread -> "Executor(UI)" | Executor StarvationModels.ForNonUIThread -> @@ -474,26 +474,22 @@ module Attribute = struct end module AttributeDomain = struct - include AbstractDomain.InvertedMap (Var) (Attribute) + include AbstractDomain.SafeInvertedMap (HilExp.AccessExpression) (Attribute) - let is_thread_guard (acc_exp : HilExp.AccessExpression.t) t = - match acc_exp with - | Base (v, _) -> - find_opt v t |> Option.exists ~f:(function Attribute.Thread -> true | _ -> false) - | _ -> - false + let is_thread_guard acc_exp t = + find_opt acc_exp t |> Option.exists ~f:(function Attribute.ThreadGuard -> true | _ -> false) - let get_executor_constraint (acc_exp : HilExp.AccessExpression.t) t = - match acc_exp with - | Base (v, _) -> - find_opt v t |> Option.bind ~f:(function Attribute.Executor c -> Some c | _ -> None) - | _ -> - None + let get_executor_constraint acc_exp t = + find_opt acc_exp t |> Option.bind ~f:(function Attribute.Executor c -> Some c | _ -> None) let exit_scope vars t = - let pred key _value = not (List.exists vars ~f:(Var.equal key)) in + let pred key _value = + HilExp.AccessExpression.get_base key + |> fst + |> fun v -> not (List.exists vars ~f:(Var.equal v)) + in filter pred t end @@ -646,26 +642,47 @@ let schedule_work loc thread_constraint astate procname = type summary = - {critical_pairs: CriticalPairs.t; thread: ThreadDomain.t; scheduled_work: ScheduledWorkDomain.t} + { critical_pairs: CriticalPairs.t + ; thread: ThreadDomain.t + ; scheduled_work: ScheduledWorkDomain.t + ; return_attribute: Attribute.t } + +let empty_summary : summary = + { critical_pairs= CriticalPairs.bottom + ; thread= ThreadDomain.bottom + ; scheduled_work= ScheduledWorkDomain.bottom + ; return_attribute= Attribute.top } + let pp_summary fmt (summary : summary) = - F.fprintf fmt "{thread= %a; critical_pairs= %a; scheduled_work= %a}" ThreadDomain.pp - summary.thread CriticalPairs.pp summary.critical_pairs ScheduledWorkDomain.pp - summary.scheduled_work + F.fprintf fmt "{thread= %a; critical_pairs= %a; scheduled_work= %a; return_attributes= %a}" + ThreadDomain.pp summary.thread CriticalPairs.pp summary.critical_pairs ScheduledWorkDomain.pp + summary.scheduled_work Attribute.pp summary.return_attribute -let integrate_summary ?tenv callsite (astate : t) (summary : summary) = +let integrate_summary ?tenv ?lhs callsite (astate : t) (summary : summary) = let critical_pairs' = CriticalPairs.with_callsite summary.critical_pairs ?tenv astate.lock_state callsite astate.thread in { astate with critical_pairs= CriticalPairs.join astate.critical_pairs critical_pairs' - ; thread= ThreadDomain.integrate_summary ~caller:astate.thread ~callee:summary.thread } + ; thread= ThreadDomain.integrate_summary ~caller:astate.thread ~callee:summary.thread + ; attributes= + Option.value_map lhs ~default:astate.attributes ~f:(fun lhs_exp -> + AttributeDomain.add lhs_exp summary.return_attribute astate.attributes ) } -let summary_of_astate : t -> summary = - fun astate -> +let summary_of_astate : Procdesc.t -> t -> summary = + fun proc_desc astate -> { critical_pairs= astate.critical_pairs ; thread= astate.thread - ; scheduled_work= astate.scheduled_work } + ; scheduled_work= astate.scheduled_work + ; return_attribute= + (let return_var_exp = + HilExp.AccessExpression.base + ( Var.of_pvar (Pvar.get_ret_pvar (Procdesc.get_proc_name proc_desc)) + , Procdesc.get_ret_type proc_desc ) + in + AttributeDomain.find_opt return_var_exp astate.attributes + |> Option.value ~default:Attribute.Nothing ) } diff --git a/infer/src/concurrency/starvationDomain.mli b/infer/src/concurrency/starvationDomain.mli index 693238d12..0334b8010 100644 --- a/infer/src/concurrency/starvationDomain.mli +++ b/infer/src/concurrency/starvationDomain.mli @@ -105,14 +105,17 @@ module GuardToLockMap : AbstractDomain.WithTop (** Tracks whether a variable has been tested for whether we execute on UI thread, or has been assigned an executor object. *) module Attribute : sig - type t = Nothing | Thread | Executor of StarvationModels.executor_thread_constraint + type t = Nothing | ThreadGuard | Executor of StarvationModels.executor_thread_constraint include AbstractDomain.WithTop with type t := t end (** Tracks all variables assigned values of [Attribute] *) module AttributeDomain : sig - include AbstractDomain.InvertedMapS with type key = Var.t and type value = Attribute.t + include + AbstractDomain.InvertedMapS + with type key = HilExp.AccessExpression.t + and type value = Attribute.t val is_thread_guard : HilExp.AccessExpression.t -> t -> bool @@ -176,12 +179,20 @@ val schedule_work : (** record the fact that a method is scheduled to run on a certain thread/executor *) type summary = - {critical_pairs: CriticalPairs.t; thread: ThreadDomain.t; scheduled_work: ScheduledWorkDomain.t} + { critical_pairs: CriticalPairs.t + ; thread: ThreadDomain.t + ; scheduled_work: ScheduledWorkDomain.t + ; return_attribute: Attribute.t } + +val empty_summary : summary val pp_summary : F.formatter -> summary -> unit -val integrate_summary : ?tenv:Tenv.t -> CallSite.t -> t -> summary -> t +val integrate_summary : + ?tenv:Tenv.t -> ?lhs:HilExp.AccessExpression.t -> CallSite.t -> t -> summary -> t +(** apply a callee summary to the current abstract state; + [lhs] is the expression assigned the returned value, if any *) -val summary_of_astate : t -> summary +val summary_of_astate : Procdesc.t -> t -> summary val filter_blocking_calls : t -> t diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java b/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java new file mode 100644 index 000000000..81690e354 --- /dev/null +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +import android.os.Binder; +import android.os.RemoteException; +import java.util.concurrent.Executor; + +class AttributeFlows { + static Binder binder; + + private static void doTransact() { + try { + binder.transact(0, null, null, 0); + } catch (RemoteException e) { + } + } + + private Executor getBackgroundExecutor() { + return Executors.getBackgroundExecutor(); + } + + private Executor indirectlyGetBackgroundExecutor() { + return getBackgroundExecutor(); + } + + private Executor getForegroundExecutor() { + return Executors.getForegroundExecutor(); + } + + private Executor indirectlyGetForegroundExecutor() { + return getForegroundExecutor(); + } + + // starvation via scheduling a transaction on UI thread + public void postBlockingCallToUIThreadBad() { + indirectlyGetForegroundExecutor() + .execute( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } + + // no report here + public void postBlockingCallToNonUIThreadOk() { + indirectlyGetBackgroundExecutor() + .execute( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } + + @ForUiThread private final Executor mUiThreadExecutor = null; + @ForNonUiThread private final Executor mNonUiThreadExecutor = null; + + private Executor getAnnotatedBackgroundExecutor() { + return mNonUiThreadExecutor; + } + + private Executor indirectlyGetAnnotatedBackgroundExecutor() { + return getAnnotatedBackgroundExecutor(); + } + + private Executor getAnnotatedForegroundExecutor() { + return mUiThreadExecutor; + } + + private Executor indirectlyGetAnnotatedForegroundExecutor() { + return getAnnotatedForegroundExecutor(); + } + + // starvation via scheduling a transaction on UI thread + public void postBlockingCallToAnnnotatedUIThreadBad() { + indirectlyGetAnnotatedForegroundExecutor() + .execute( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } + + // no report here + public void postBlockingCallToAnnotatedNonUIThreadOk() { + indirectlyGetAnnotatedBackgroundExecutor() + .execute( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } +} diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/Executors.java b/infer/tests/codetoanalyze/java/starvation-whole-program/Executors.java new file mode 100644 index 000000000..eaf431b16 --- /dev/null +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/Executors.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import java.util.concurrent.Executor; + +// modeled executors +class Executors { + static Executor uiExecutor; + + static Executor getForegroundExecutor() { + return uiExecutor; + } + + static Executor bgExecutor; + + static Executor getBackgroundExecutor() { + return bgExecutor; + } + + public static void postOnUiThread(Runnable runnable) {} + + public static void runOnUiThread(Runnable runnable) {} + + public static void postOnUiThreadDelayed(Runnable runnable, long delayMs) {} + + public static void scheduleGuaranteedDelayed( + Runnable job, long delayMillis, long lastExecution) {} +} diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java b/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java index f9dbce4f7..ded549147 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java @@ -7,7 +7,6 @@ import android.os.Binder; import android.os.RemoteException; -import java.util.concurrent.Executor; class ModeledExecutors { static Binder binder; @@ -116,27 +115,3 @@ class ModeledExecutors { }); } } - -// modeled executors -class Executors { - static Executor uiExecutor; - - static Executor getForegroundExecutor() { - return uiExecutor; - } - - static Executor bgExecutor; - - static Executor getBackgroundExecutor() { - return bgExecutor; - } - - public static void postOnUiThread(Runnable runnable) {} - - public static void runOnUiThread(Runnable runnable) {} - - public static void postOnUiThreadDelayed(Runnable runnable, long delayMs) {} - - public static void scheduleGuaranteedDelayed( - Runnable job, long delayMillis, long lastExecution) {} -} diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp index 7206eea51..a75b89e7a 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp @@ -1,15 +1,17 @@ +codetoanalyze/java/starvation-whole-program/AttributeFlows.java, AttributeFlows.postBlockingCallToAnnnotatedUIThreadBad():void, 84, STARVATION, no_bucket, ERROR, [`void AttributeFlows.postBlockingCallToAnnnotatedUIThreadBad()`,Method call: `void AttributeFlows$3.run()`,Method call: `void AttributeFlows.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/AttributeFlows.java, AttributeFlows.postBlockingCallToUIThreadBad():void, 41, STARVATION, no_bucket, ERROR, [`void AttributeFlows.postBlockingCallToUIThreadBad()`,Method call: `void AttributeFlows$1.run()`,Method call: `void AttributeFlows.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/Deadlock.java, Deadlock.postDeadlockBad():void, 19, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void Deadlock.postDeadlockBad()`,Method call: `void Deadlock$1.run()`, locks `this.monitorA` in `class Deadlock`, locks `this.monitorB` in `class Deadlock`,[Trace 2] `void Deadlock.postDeadlockBad()`,Method call: `void Deadlock$2.run()`, locks `this.monitorB` in `class Deadlock`, locks `this.monitorA` in `class Deadlock`] codetoanalyze/java/starvation-whole-program/Deadlock.java, Deadlock.postDeadlockBad():void, 30, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void Deadlock.postDeadlockBad()`,Method call: `void Deadlock$2.run()`, locks `this.monitorB` in `class Deadlock`, locks `this.monitorA` in `class Deadlock`,[Trace 2] `void Deadlock.postDeadlockBad()`,Method call: `void Deadlock$1.run()`, locks `this.monitorA` in `class Deadlock`, locks `this.monitorB` in `class Deadlock`] codetoanalyze/java/starvation-whole-program/Deadlock.java, Deadlock.postOnBGThreadBad():void, 73, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void Deadlock.postOnBGThreadBad()`,Method call: `void Deadlock$5.run()`, locks `this.monitorE` in `class Deadlock`, locks `this.monitorF` in `class Deadlock`,[Trace 2] `void Deadlock.postOnBGThreadBad()`,Method call: `void Deadlock$6.run()`, locks `this.monitorF` in `class Deadlock`, locks `this.monitorE` in `class Deadlock`] codetoanalyze/java/starvation-whole-program/Deadlock.java, Deadlock.postOnBGThreadBad():void, 84, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void Deadlock.postOnBGThreadBad()`,Method call: `void Deadlock$6.run()`, locks `this.monitorF` in `class Deadlock`, locks `this.monitorE` in `class Deadlock`,[Trace 2] `void Deadlock.postOnBGThreadBad()`,Method call: `void Deadlock$5.run()`, locks `this.monitorE` in `class Deadlock`, locks `this.monitorF` in `class Deadlock`] codetoanalyze/java/starvation-whole-program/DirectStarvation.java, DirectStarvation.postBlockingCallToUIThreadBad():void, 29, STARVATION, no_bucket, ERROR, [`void DirectStarvation.postBlockingCallToUIThreadBad()`,Method call: `void DirectStarvation$1.run()`,Method call: `void DirectStarvation.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/IndirectStarvation.java, IndirectStarvation.postBlockingCallToBackgroundThreadAndLockBad():void, 32, STARVATION, no_bucket, ERROR, [[Trace 1] `void IndirectStarvation.postBlockingCallToBackgroundThreadAndLockBad()`,Method call: `void IndirectStarvation$1.run()`, locks `this.monitorA` in `class IndirectStarvation`,[Trace 2] `void IndirectStarvation.postBlockingCallToBackgroundThreadAndLockBad()`,Method call: `void IndirectStarvation$2.run()`, locks `this.monitorA` in `class IndirectStarvation`,Method call: `void IndirectStarvation.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.postBlockingCallToUIThreadBad():void, 25, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.postBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$1.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad():void, 94, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$7.run()`, locks `this.monitorA` in `class ModeledExecutors`, locks `this.monitorB` in `class ModeledExecutors`,[Trace 2] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$8.run()`, locks `this.monitorB` in `class ModeledExecutors`, locks `this.monitorA` in `class ModeledExecutors`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad():void, 107, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$8.run()`, locks `this.monitorB` in `class ModeledExecutors`, locks `this.monitorA` in `class ModeledExecutors`,[Trace 2] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$7.run()`, locks `this.monitorA` in `class ModeledExecutors`, locks `this.monitorB` in `class ModeledExecutors`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticPostBlockingCallToUIThreadBad():void, 47, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticPostBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$3.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticPostDelayedBlockingCallToUIThreadBad():void, 69, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticPostDelayedBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$5.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] -codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticRunBlockingCallToUIThreadBad():void, 58, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticRunBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$4.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.postBlockingCallToUIThreadBad():void, 24, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.postBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$1.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad():void, 93, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$7.run()`, locks `this.monitorA` in `class ModeledExecutors`, locks `this.monitorB` in `class ModeledExecutors`,[Trace 2] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$8.run()`, locks `this.monitorB` in `class ModeledExecutors`, locks `this.monitorA` in `class ModeledExecutors`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad():void, 106, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$8.run()`, locks `this.monitorB` in `class ModeledExecutors`, locks `this.monitorA` in `class ModeledExecutors`,[Trace 2] `void ModeledExecutors.scheduleGuaranteedDelayedDeadlockBad()`,Method call: `void ModeledExecutors$7.run()`, locks `this.monitorA` in `class ModeledExecutors`, locks `this.monitorB` in `class ModeledExecutors`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticPostBlockingCallToUIThreadBad():void, 46, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticPostBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$3.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticPostDelayedBlockingCallToUIThreadBad():void, 68, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticPostDelayedBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$5.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/ModeledExecutors.java, ModeledExecutors.staticRunBlockingCallToUIThreadBad():void, 57, STARVATION, no_bucket, ERROR, [`void ModeledExecutors.staticRunBlockingCallToUIThreadBad()`,Method call: `void ModeledExecutors$4.run()`,Method call: `void ModeledExecutors.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/MyActivity.java, MyActivity.onCreate(android.os.Bundle):void, 28, STARVATION, no_bucket, ERROR, [`void MyActivity.onCreate(Bundle)`,Method call: `void MyActivity.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/MyActivity.java, MyActivity.onDestroy():void, 68, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void MyActivity.onDestroy()`, locks `this.monitorC` in `class MyActivity`, locks `this.monitorB` in `class MyActivity`,[Trace 2] `void MyActivity.onPause()`,Method call: `void MyActivity$2.run()`, locks `this.monitorB` in `class MyActivity`, locks `this.monitorC` in `class MyActivity`] codetoanalyze/java/starvation-whole-program/MyActivity.java, MyActivity.onPause():void, 76, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void MyActivity.onPause()`,Method call: `void MyActivity$2.run()`, locks `this.monitorB` in `class MyActivity`, locks `this.monitorC` in `class MyActivity`,[Trace 2] `void MyActivity.onDestroy()`, locks `this.monitorC` in `class MyActivity`, locks `this.monitorB` in `class MyActivity`]