From bad9ab08e760d2f3f3efbe5aaa7e066e11d7a349 Mon Sep 17 00:00:00 2001 From: Nikos Gorogiannis Date: Mon, 1 Jun 2020 05:56:38 -0700 Subject: [PATCH] [starvation][global] add android View models Summary: Add models for `View` methods that schedule on the UI thread. Reviewed By: skcho Differential Revision: D21767954 fbshipit-source-id: 015441ea7 --- infer/src/concurrency/StarvationModels.ml | 35 ++++++++++----- infer/src/concurrency/StarvationModels.mli | 14 ++++-- infer/src/concurrency/starvation.ml | 7 ++- .../java/starvation-whole-program/MyView.java | 45 +++++++++++++++++++ .../java/starvation-whole-program/issues.exp | 2 + 5 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 infer/tests/codetoanalyze/java/starvation-whole-program/MyView.java diff --git a/infer/src/concurrency/StarvationModels.ml b/infer/src/concurrency/StarvationModels.ml index 50e152003..d0fc76792 100644 --- a/infer/src/concurrency/StarvationModels.ml +++ b/infer/src/concurrency/StarvationModels.ml @@ -252,24 +252,35 @@ let schedules_work = fun tenv pname -> matcher tenv pname [] -let schedules_work_on_ui_thread = +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_records + { 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_work_on_bg_thread = +let schedules_first_arg_on_bg_thread = let open MethodMatcher in let matcher = [ {default with classname= "java.lang.Object"; methods= ["scheduleGuaranteedDelayed"]} diff --git a/infer/src/concurrency/StarvationModels.mli b/infer/src/concurrency/StarvationModels.mli index e06fd59f5..47516e103 100644 --- a/infer/src/concurrency/StarvationModels.mli +++ b/infer/src/concurrency/StarvationModels.mli @@ -52,11 +52,17 @@ val get_returned_executor : Tenv.t -> Procname.t -> HilExp.t list -> scheduler_thread_constraint option (** does the function return an executor and of which thread? *) -val schedules_work_on_ui_thread : Tenv.t -> Procname.t -> bool -(** method call known to directly schedule work on UI thread *) +val schedules_first_arg_on_ui_thread : Tenv.t -> Procname.t -> bool +(** method call known to directly schedule the runnable object provided as first procedure argument + on the UI thread *) -val schedules_work_on_bg_thread : Tenv.t -> Procname.t -> bool -(** method call known to directly schedule work on BG thread *) +val schedules_second_arg_on_ui_thread : Tenv.t -> Procname.t -> bool +(** method call known to directly schedule the runnable object provided as second procedure argument + on a background thread *) + +val schedules_first_arg_on_bg_thread : Tenv.t -> Procname.t -> bool +(** method call known to directly the runnable object provided as first procedure argument on a + background thread *) val is_getMainLooper : Tenv.t -> Procname.t -> HilExp.t list -> bool diff --git a/infer/src/concurrency/starvation.ml b/infer/src/concurrency/starvation.ml index e2235c539..5aa5acac9 100644 --- a/infer/src/concurrency/starvation.ml +++ b/infer/src/concurrency/starvation.ml @@ -95,9 +95,12 @@ module TransferFunctions (CFG : ProcCfg.S) = struct |> Option.value ~default:StarvationModels.ForUnknownThread in Some (runnable, thread) - | Some runnable :: _ when StarvationModels.schedules_work_on_ui_thread tenv callee -> + | Some runnable :: _ when StarvationModels.schedules_first_arg_on_ui_thread tenv callee -> Some (runnable, StarvationModels.ForUIThread) - | Some runnable :: _ when StarvationModels.schedules_work_on_bg_thread tenv callee -> + | _ :: Some runnable :: _ when StarvationModels.schedules_second_arg_on_ui_thread tenv callee + -> + Some (runnable, StarvationModels.ForUIThread) + | Some runnable :: _ when StarvationModels.schedules_first_arg_on_bg_thread tenv callee -> Some (runnable, StarvationModels.ForNonUIThread) | _ -> None diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/MyView.java b/infer/tests/codetoanalyze/java/starvation-whole-program/MyView.java new file mode 100644 index 000000000..6c3eaeed8 --- /dev/null +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/MyView.java @@ -0,0 +1,45 @@ +/* + * 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.content.Context; +import android.view.View; +import java.util.concurrent.Executor; + +class MyView extends View { + MyView(Context c) { + super(c); + } + + Object monitorA, monitorB; + @ForNonUiThread private final Executor mNonUiThreadExecutor = null; + + void scheduleOnBGThread() { + mNonUiThreadExecutor.execute( + new Runnable() { + @Override + public void run() { + synchronized (monitorA) { + synchronized (monitorB) { + } + } + } + }); + } + + void scheduleOnUIThread() { + post( + new Runnable() { + @Override + public void run() { + synchronized (monitorB) { + synchronized (monitorA) { + } + } + } + }); + } +} diff --git a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp index 9e3f1afa8..ebd47cd83 100644 --- a/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp +++ b/infer/tests/codetoanalyze/java/starvation-whole-program/issues.exp @@ -31,6 +31,8 @@ codetoanalyze/java/starvation-whole-program/MyActivity.java, MyActivity.onStart( codetoanalyze/java/starvation-whole-program/MyActivity.java, MyActivity.onStop():void, 48, STARVATION, no_bucket, ERROR, [[Trace 1] `void MyActivity.onStop()`, locks `this.monitorA` in `class MyActivity`,[Trace 2] `void MyActivity.onStop()`,Method call: `void MyActivity$1.run()`, locks `this.this$0.monitorA` in `class MyActivity$1`,Method call: `void MyActivity.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/MyServiceConnection.java, MyServiceConnection.onServiceConnected(android.content.ComponentName,android.os.IBinder):void, 37, STARVATION, no_bucket, ERROR, [`void MyServiceConnection.onServiceConnected(ComponentName,IBinder)`,Method call: `void MyServiceConnection.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/MyServiceConnection.java, MyServiceConnection.onServiceDisconnected(android.content.ComponentName):void, 42, STARVATION, no_bucket, ERROR, [`void MyServiceConnection.onServiceDisconnected(ComponentName)`,Method call: `void MyServiceConnection.bad()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] +codetoanalyze/java/starvation-whole-program/MyView.java, MyView.scheduleOnBGThread():void, 21, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void MyView.scheduleOnBGThread()`,Method call: `void MyView$1.run()`, locks `this.this$0.monitorA` in `class MyView$1`, locks `this.this$0.monitorB` in `class MyView$1`,[Trace 2] `void MyView.scheduleOnUIThread()`,Method call: `void MyView$2.run()`, locks `this.this$0.monitorB` in `class MyView$2`, locks `this.this$0.monitorA` in `class MyView$2`] +codetoanalyze/java/starvation-whole-program/MyView.java, MyView.scheduleOnUIThread():void, 34, DEADLOCK, no_bucket, ERROR, [[Trace 1] `void MyView.scheduleOnUIThread()`,Method call: `void MyView$2.run()`, locks `this.this$0.monitorB` in `class MyView$2`, locks `this.this$0.monitorA` in `class MyView$2`,[Trace 2] `void MyView.scheduleOnBGThread()`,Method call: `void MyView$1.run()`, locks `this.this$0.monitorA` in `class MyView$1`, locks `this.this$0.monitorB` in `class MyView$1`] codetoanalyze/java/starvation-whole-program/StaticInitAttributes.java, StaticInitAttributes.postBlockingCallToUIExecutorBad():void, 52, STARVATION, no_bucket, ERROR, [`void StaticInitAttributes.postBlockingCallToUIExecutorBad()`,Method call: `void StaticInitAttributes$1.run()`,Method call: `void StaticInitAttributes.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/StaticInitAttributes.java, StaticInitAttributes.postBlockingCallToUIHandlerBad():void, 64, STARVATION, no_bucket, ERROR, [`void StaticInitAttributes.postBlockingCallToUIHandlerBad()`,Method call: `void StaticInitAttributes$1.run()`,Method call: `void StaticInitAttributes.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`] codetoanalyze/java/starvation-whole-program/ThreadScheduling.java, ThreadScheduling.scheduleBlockingCallOnContendedLockBad():void, 36, STARVATION, no_bucket, ERROR, [[Trace 1] `void ThreadScheduling.scheduleBlockingCallOnContendedLockBad()`,Method call: `void ThreadScheduling$2.run()`, locks `this.this$0.monitorA` in `class ThreadScheduling$2`,[Trace 2] `void ThreadScheduling.scheduleBlockingCallOnContendedLockBad()`,Method call: `void ThreadScheduling$1.run()`, locks `this.this$0.monitorA` in `class ThreadScheduling$1`,Method call: `void ThreadScheduling.doTransact()`,calls `boolean Binder.transact(int,Parcel,Parcel,int)`]