diff --git a/infer/src/concurrency/starvation.ml b/infer/src/concurrency/starvation.ml index ccc84b1ee..556627fce 100644 --- a/infer/src/concurrency/starvation.ml +++ b/infer/src/concurrency/starvation.ml @@ -72,19 +72,27 @@ module TransferFunctions (CFG : ProcCfg.S) = struct astate + (* get attribute of an expression, and if there is none, check if the expression can + be implicitly ascribed a property (runnable/executor). *) let get_exp_attributes tenv exp (astate : Domain.t) = let open Domain in 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) ) + |> IOption.if_none_evalopt ~f:(fun () -> + StarvationModels.get_run_method_from_runnable tenv exp + |> Option.map ~f:(fun procname -> Attribute.Runnable procname) ) 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) + let schedule_work runnable thread = + match get_exp_attributes tenv runnable astate with + | Some (Attribute.Runnable procname) -> + Domain.schedule_work loc thread astate procname + | _ -> + astate in match actuals with | [HilExp.AccessExpression executor; HilExp.AccessExpression runnable] @@ -96,13 +104,13 @@ module TransferFunctions (CFG : ProcCfg.S) = struct | _ -> StarvationModels.ForUnknownThread in - schedule_work runnable astate thread + schedule_work runnable thread | HilExp.AccessExpression runnable :: _ when StarvationModels.schedules_work_on_ui_thread tenv callee -> - schedule_work runnable astate StarvationModels.ForUIThread + schedule_work runnable StarvationModels.ForUIThread | HilExp.AccessExpression runnable :: _ when StarvationModels.schedules_work_on_bg_thread tenv callee -> - schedule_work runnable astate StarvationModels.ForNonUIThread + schedule_work runnable StarvationModels.ForNonUIThread | _ -> astate diff --git a/infer/src/concurrency/starvationDomain.ml b/infer/src/concurrency/starvationDomain.ml index 25232ea61..0caf03cb3 100644 --- a/infer/src/concurrency/starvationDomain.ml +++ b/infer/src/concurrency/starvationDomain.ml @@ -444,7 +444,11 @@ module GuardToLockMap = struct end module Attribute = struct - type t = Nothing | ThreadGuard | Executor of StarvationModels.executor_thread_constraint + type t = + | Nothing + | ThreadGuard + | Executor of StarvationModels.executor_thread_constraint + | Runnable of Typ.Procname.t [@@deriving equal] let top = Nothing @@ -452,18 +456,20 @@ module Attribute = struct let is_top = function Nothing -> true | _ -> false let pp fmt t = - ( match t with + let pp_constr fmt c = + StarvationModels.( + match c with ForUIThread -> "UI" | ForNonUIThread -> "BG" | ForUnknownThread -> "Unknown") + |> F.pp_print_string fmt + in + match t with | Nothing -> - "Nothing" + F.pp_print_string fmt "Nothing" | ThreadGuard -> - "ThreadGuard" - | Executor StarvationModels.ForUIThread -> - "Executor(UI)" - | Executor StarvationModels.ForNonUIThread -> - "Executor(BG)" - | Executor StarvationModels.ForUnknownThread -> - "Executor(Unknown)" ) - |> F.pp_print_string fmt + F.pp_print_string fmt "ThreadGuard" + | Executor c -> + F.fprintf fmt "Executor(%a)" pp_constr c + | Runnable runproc -> + F.fprintf fmt "Runnable(%a)" Typ.Procname.pp runproc let join lhs rhs = if equal lhs rhs then lhs else Nothing diff --git a/infer/src/concurrency/starvationDomain.mli b/infer/src/concurrency/starvationDomain.mli index 0334b8010..1b0811d71 100644 --- a/infer/src/concurrency/starvationDomain.mli +++ b/infer/src/concurrency/starvationDomain.mli @@ -105,7 +105,11 @@ 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 | ThreadGuard | Executor of StarvationModels.executor_thread_constraint + type t = + | Nothing + | ThreadGuard + | Executor of StarvationModels.executor_thread_constraint + | Runnable of Typ.Procname.t include AbstractDomain.WithTop with type t := t end diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java b/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java index 81690e354..fd967f6d7 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/AttributeFlows.java @@ -101,4 +101,29 @@ class AttributeFlows { } }); } + + Runnable getBadRunnable() { + return new Runnable() { + @Override + public void run() { + doTransact(); + } + }; + } + + public void postRunnableIndirectlyToUIThreadBad() { + Executors.getForegroundExecutor().execute(getBadRunnable()); + } + + Runnable runnableField = + new Runnable() { + @Override + public void run() { + doTransact(); + } + }; + + public void FN_postRunnableFieldToUIThreadBad() { + Executors.getForegroundExecutor().execute(runnableField); + } } diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp index a75b89e7a..315344216 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp @@ -1,5 +1,6 @@ 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/AttributeFlows.java, AttributeFlows.postRunnableIndirectlyToUIThreadBad():void, 115, STARVATION, no_bucket, ERROR, [`void AttributeFlows.postRunnableIndirectlyToUIThreadBad()`,Method call: `void AttributeFlows$5.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`]