diff --git a/infer/src/concurrency/StarvationModels.ml b/infer/src/concurrency/StarvationModels.ml index cb3da38ed..20f6a34ec 100644 --- a/infer/src/concurrency/StarvationModels.ml +++ b/infer/src/concurrency/StarvationModels.ml @@ -212,6 +212,17 @@ let schedules_work = fun tenv pname -> matcher tenv pname [] +let schedules_work_on_ui_thread = + let open MethodMatcher in + let matcher = + [ { default with + classname= "java.lang.Object" + ; methods= ["postOnUiThread"; "runOnUiThread"; "postOnUiThreadDelayed"] } ] + |> of_records + in + fun tenv pname -> matcher tenv pname [] + + type executor_thread_constraint = ForUIThread | ForNonUIThread [@@deriving equal] (* Executors are usually stored in fields and annotated according to what type of thread diff --git a/infer/src/concurrency/StarvationModels.mli b/infer/src/concurrency/StarvationModels.mli index 3aa76f5e3..4b6a898ab 100644 --- a/infer/src/concurrency/StarvationModels.mli +++ b/infer/src/concurrency/StarvationModels.mli @@ -52,3 +52,6 @@ val get_executor_effect : -> HilExp.t list -> executor_thread_constraint option (** does the function return an executor and of which thread? *) + +val schedules_work_on_ui_thread : Tenv.t -> Typ.Procname.t -> bool +(** method call known to directly schedule work on UI thread *) diff --git a/infer/src/concurrency/starvation.ml b/infer/src/concurrency/starvation.ml index 0bb125d75..79db70988 100644 --- a/infer/src/concurrency/starvation.ml +++ b/infer/src/concurrency/starvation.ml @@ -102,6 +102,9 @@ module TransferFunctions (CFG : ProcCfg.S) = struct lazy (Domain.AttributeDomain.get_executor_constraint executor astate.attributes) ] in Option.fold thread_opt ~init:astate ~f:(schedule_work runnable) + | HilExp.AccessExpression runnable :: _ + when StarvationModels.schedules_work_on_ui_thread tenv callee -> + schedule_work runnable astate StarvationModels.ForUIThread | _ -> astate diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java b/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java index 04f4cbdaa..bdc5eecdf 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/ModeledExecutors.java @@ -41,6 +41,40 @@ class ModeledExecutors { } }); } + + // starvation via posting a transaction on UI thread + public void staticPostBlockingCallToUIThreadBad() { + Executors.postOnUiThread( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } + + // starvation via running a transaction on UI thread + public void staticRunBlockingCallToUIThreadBad() { + Executors.runOnUiThread( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }); + } + + // starvation via running a delayed transaction on UI thread + public void staticPostDelayedBlockingCallToUIThreadBad() { + Executors.postOnUiThreadDelayed( + new Runnable() { + @Override + public void run() { + doTransact(); + } + }, + 1000L); + } } // modeled executors @@ -56,4 +90,10 @@ class Executors { 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) {} } diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp index a6b87e8fa..7b4abe658 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp @@ -5,6 +5,9 @@ codetoanalyze/java/starvation-whole-program/Deadlock.java, Deadlock.postOnBGThre 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.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/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`]