[starvation][whole-program] track runnables as attributes

Summary:
Instead of trying to figure out what runnable is directly passed to an executor,
use attributes to track the flow of a runnable.  This has several advantages:
- Can track runnables across function return values.
- Can somewhat overcome the information loss under dynamic dispatch.
- Unifies handling with other attributes.

Reviewed By: skcho

Differential Revision: D18672676

fbshipit-source-id: a06a0e6ff
master
Nikos Gorogiannis 5 years ago committed by Facebook Github Bot
parent 20a7e9d75b
commit 404caf3bb4

@ -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

@ -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

@ -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

@ -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);
}
}

@ -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`]

Loading…
Cancel
Save