[starvation] refactor method matching ; kill dev-android-strict-mode option

Summary:
The method matcher is now used sufficiently it warrants refactoring out into its own module.

Also, kill dev-android-strict-mode and leave starvation-strict-mode as the stronger option.

Reviewed By: jeremydubreil

Differential Revision: D9990753

fbshipit-source-id: 626a70a19
master
Nikos Gorogiannis 6 years ago committed by Facebook Github Bot
parent 90fd5ec4a4
commit 631959ced0

@ -92,8 +92,7 @@ BUILD_SYSTEMS_TESTS += \
DIRECT_TESTS += \ DIRECT_TESTS += \
java_checkers java_eradicate java_infer java_lab java_tracing java_quandary \ java_checkers java_eradicate java_infer java_lab java_tracing java_quandary \
java_racerd java_stability java_crashcontext java_hoisting java_starvation java_performance \ java_racerd java_stability java_crashcontext java_hoisting java_starvation java_performance
java_devstrictmode
ifneq ($(ANT),no) ifneq ($(ANT),no)
BUILD_SYSTEMS_TESTS += ant BUILD_SYSTEMS_TESTS += ant
endif endif

@ -2105,12 +2105,6 @@ and stats_report =
"Write a report of the analysis results to a file" "Write a report of the analysis results to a file"
and dev_android_strict_mode =
CLOpt.mk_bool ~long:"dev-android-strict-mode" ~default:false
"Developer mode for starvation analysis, only for use on android implementation sources; \
detects methods in the Android library core which throw strict mode violation exceptions"
and subtype_multirange = and subtype_multirange =
CLOpt.mk_bool ~deprecated:["subtype_multirange"] ~long:"subtype-multirange" ~default:true CLOpt.mk_bool ~deprecated:["subtype_multirange"] ~long:"subtype-multirange" ~default:true
"Use the multirange subtyping domain" "Use the multirange subtyping domain"
@ -2626,8 +2620,6 @@ and default_linters = !default_linters
and dependency_mode = !dependencies and dependency_mode = !dependencies
and dev_android_strict_mode = !dev_android_strict_mode
and developer_mode = !developer_mode and developer_mode = !developer_mode
and differential_filter_files = !differential_filter_files and differential_filter_files = !differential_filter_files

@ -328,8 +328,6 @@ val default_linters : bool
val dependency_mode : bool val dependency_mode : bool
val dev_android_strict_mode : bool
val developer_mode : bool val developer_mode : bool
val differential_filter_files : string option val differential_filter_files : string option

@ -7,7 +7,6 @@
open! IStd open! IStd
module F = Format module F = Format
module L = Logging
module MF = MarkupFormatter module MF = MarkupFormatter
type lock = Lock | Unlock | LockedIfTrue | NoEffect type lock = Lock | Unlock | LockedIfTrue | NoEffect
@ -159,92 +158,9 @@ let find_annotated_or_overriden_annotated_method is_annot pname tenv =
tenv pname tenv pname
let is_call_of_class ?(search_superclasses = true) ?(method_prefix = false)
?(actuals_pred = fun _ -> true) clazz methods =
let method_matcher =
if method_prefix then fun current_method target_method ->
String.is_prefix current_method ~prefix:target_method
else fun current_method target_method -> String.equal current_method target_method
in
let class_matcher =
let is_target_class =
let target = Typ.Name.Java.from_string clazz in
fun tname -> Typ.Name.equal tname target
in
if search_superclasses then fun tenv classname ->
let is_target_struct tname _ = is_target_class tname in
PatternMatch.supertype_exists tenv is_target_struct classname
else fun _ classname -> is_target_class classname
in
(fun tenv pn actuals ->
actuals_pred actuals
&&
match pn with
| Typ.Procname.Java java_pname ->
let mthd = Typ.Procname.Java.get_method java_pname in
List.exists methods ~f:(method_matcher mthd)
&&
let classname = Typ.Procname.Java.get_class_type_name java_pname in
class_matcher tenv classname
| _ ->
false )
|> Staged.stage
type matcher = Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool
type java_matcher_record =
{ search_superclasses: bool option
; method_prefix: bool option
; actuals_pred: (HilExp.t list -> bool) option
; classname: string
; methods: string list }
let make_matcher_from_record {search_superclasses; method_prefix; actuals_pred; classname; methods}
=
is_call_of_class ?search_superclasses ?method_prefix ?actuals_pred classname methods
|> Staged.unstage
let empty_matcher =
{search_superclasses= None; method_prefix= None; actuals_pred= None; classname= ""; methods= []}
let matcher_of_json top_json =
let error json =
L.(die UserError "Could not parse json matcher(s): %s" (Yojson.Basic.to_string json))
in
let make_matcher_from_json json =
let parse_method_name = function `String methodname -> methodname | _ -> error json in
let rec parse_fields assoclist acc =
match assoclist with
| ("search_superclasses", `Bool b) :: rest ->
{acc with search_superclasses= Some b} |> parse_fields rest
| ("method_prefix", `Bool b) :: rest ->
{acc with method_prefix= Some b} |> parse_fields rest
| ("classname", `String classname) :: rest ->
{acc with classname} |> parse_fields rest
| ("methods", `List methodnames) :: rest ->
let methods = List.map methodnames ~f:parse_method_name in
{acc with methods} |> parse_fields rest
| [] ->
if String.equal acc.classname "" || List.is_empty acc.methods then error json else acc
| _ ->
error json
in
(match json with `Assoc fields -> parse_fields fields empty_matcher | _ -> error json)
|> make_matcher_from_record
in
match top_json with
| `List matchers_json ->
let matchers = List.map matchers_json ~f:make_matcher_from_json in
fun tenv pn actuals -> List.exists matchers ~f:(fun m -> m tenv pn actuals)
| _ ->
error top_json
let ui_matcher_records = let ui_matcher_records =
let superclasses = {empty_matcher with search_superclasses= Some true} in let open MethodMatcher in
let superclasses = {empty with search_superclasses= Some true} in
let fragment_methods = let fragment_methods =
(* sort police: this is in lifecycle order *) (* sort police: this is in lifecycle order *)
[ "onAttach" [ "onAttach"
@ -280,9 +196,9 @@ let ui_matcher_records =
let is_ui_method = let is_ui_method =
let matchers = List.map ui_matcher_records ~f:make_matcher_from_record in let matchers = List.map ui_matcher_records ~f:MethodMatcher.of_record in
(* we pass an empty actuals list because all matching is done on class and method name here *) (* we pass an empty actuals list because all matching is done on class and method name here *)
fun tenv pname -> List.exists matchers ~f:(fun m -> m tenv pname []) fun tenv pname -> MethodMatcher.of_list matchers tenv pname []
let runs_on_ui_thread = let runs_on_ui_thread =

@ -34,29 +34,3 @@ val get_current_class_and_annotated_superclasses :
val find_annotated_or_overriden_annotated_method : val find_annotated_or_overriden_annotated_method :
(Annot.Item.t -> bool) -> BuiltinDecl.t -> Tenv.t -> BuiltinDecl.t sexp_option (Annot.Item.t -> bool) -> BuiltinDecl.t -> Tenv.t -> BuiltinDecl.t sexp_option
(** pattern matcher for Java methods *)
type matcher = Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool
val is_call_of_class :
?search_superclasses:bool
-> ?method_prefix:bool
-> ?actuals_pred:(HilExp.t list -> bool)
-> string
-> string list
-> matcher Staged.t
(** [is_call_of_class C methods] builds a method matcher for calls [C.foo] where
[foo] is in [methods]. Optional arguments change default behaviour:
- [search_superclasses=true] will match calls [S.foo] where [S] is a superclass of [C].
Defaults to [false].
- [method_prefix=true] will match calls [C.foo] where [foo] is a prefix of a string in [methods]
Defaults to [false].
- [actuals_pred] is a predicate that runs on the expressions fed as arguments to the call, and
which must return [true] for the matcher to return [true]. The default returns [true]. *)
val matcher_of_json : Yojson.Basic.json -> matcher
(** Parse a JSon object into a matcher. The Json object must be a list of records, each
corresponding to a single matcher. Each record must have a ["classname"] field with a [string]
value, and a ["methods"] field with a list of strings. The record may also have boolean
fields ["search_superclasses"] and ["method_prefix"]. If absent, the defaults are used.
The resulting matcher matches if one of the matchers in the list does. *)

@ -0,0 +1,91 @@
(*
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module L = Logging
let call_matches ?(search_superclasses = true) ?(method_prefix = false)
?(actuals_pred = fun _ -> true) clazz methods =
let method_matcher =
if method_prefix then fun current_method target_method ->
String.is_prefix current_method ~prefix:target_method
else fun current_method target_method -> String.equal current_method target_method
in
let class_matcher =
let is_target_class =
let target = Typ.Name.Java.from_string clazz in
fun tname -> Typ.Name.equal tname target
in
if search_superclasses then fun tenv classname ->
let is_target_struct tname _ = is_target_class tname in
PatternMatch.supertype_exists tenv is_target_struct classname
else fun _ classname -> is_target_class classname
in
(fun tenv pn actuals ->
actuals_pred actuals
&&
match pn with
| Typ.Procname.Java java_pname ->
let mthd = Typ.Procname.Java.get_method java_pname in
List.exists methods ~f:(method_matcher mthd)
&&
let classname = Typ.Procname.Java.get_class_type_name java_pname in
class_matcher tenv classname
| _ ->
false )
|> Staged.stage
type t = Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool
type record =
{ search_superclasses: bool option
; method_prefix: bool option
; actuals_pred: (HilExp.t list -> bool) option
; classname: string
; methods: string list }
let of_record {search_superclasses; method_prefix; actuals_pred; classname; methods} =
call_matches ?search_superclasses ?method_prefix ?actuals_pred classname methods
|> Staged.unstage
let empty =
{search_superclasses= None; method_prefix= None; actuals_pred= None; classname= ""; methods= []}
let of_list matchers tenv pn actuals = List.exists matchers ~f:(fun m -> m tenv pn actuals)
let of_json top_json =
let error json =
L.(die UserError "Could not parse json matcher(s): %s" (Yojson.Basic.to_string json))
in
let make_matcher_from_json json =
let parse_method_name = function `String methodname -> methodname | _ -> error json in
let rec parse_fields assoclist acc =
match assoclist with
| ("search_superclasses", `Bool b) :: rest ->
{acc with search_superclasses= Some b} |> parse_fields rest
| ("method_prefix", `Bool b) :: rest ->
{acc with method_prefix= Some b} |> parse_fields rest
| ("classname", `String classname) :: rest ->
{acc with classname} |> parse_fields rest
| ("methods", `List methodnames) :: rest ->
let methods = List.map methodnames ~f:parse_method_name in
{acc with methods} |> parse_fields rest
| [] ->
if String.equal acc.classname "" || List.is_empty acc.methods then error json else acc
| _ ->
error json
in
(match json with `Assoc fields -> parse_fields fields empty | _ -> error json) |> of_record
in
match top_json with
| `List matchers_json ->
List.map matchers_json ~f:make_matcher_from_json |> of_list
| _ ->
error top_json

@ -0,0 +1,51 @@
(*
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
(** pattern matcher for Java methods *)
type t = Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool
val call_matches :
?search_superclasses:bool
-> ?method_prefix:bool
-> ?actuals_pred:(HilExp.t list -> bool)
-> string
-> string list
-> t Staged.t
[@@warning "-32"]
(** [call_matches C methods] builds a method matcher for calls [C.foo] where
[foo] is in [methods]. Optional arguments change default behaviour:
- [search_superclasses=true] will match calls [S.foo] where [S] is a superclass of [C].
Defaults to [true].
- [method_prefix=true] will match calls [C.foo] where [foo] is a prefix of a string in [methods]
Defaults to [false].
- [actuals_pred] is a predicate that runs on the expressions fed as arguments to the call, and
which must return [true] for the matcher to return [true]. The default returns [true]. *)
type record =
{ search_superclasses: bool option
; method_prefix: bool option
; actuals_pred: (HilExp.t list -> bool) option
; classname: string
; methods: string list }
val empty : record
(** record with empty strings, lists and None where appropriate. Useful for [with] expressions *)
val of_record : record -> t
(** make a matcher out of a record; optional values use defaults *)
val of_json : Yojson.Basic.json -> t
(** Parse a JSon object into a matcher. The Json object must be a list of records, each
corresponding to a single matcher. Each record must have a ["classname"] field with a [string]
value, and a ["methods"] field with a list of strings. The record may also have boolean
fields ["search_superclasses"] and ["method_prefix"]. If absent, the defaults are used.
The resulting matcher matches if one of the matchers in the list does. *)
val of_list : t list -> t
(** Or combinator *)

@ -6,7 +6,6 @@
*) *)
open! IStd open! IStd
open ConcurrencyModels
let is_synchronized_library_call = let is_synchronized_library_call =
let targets = ["java.lang.StringBuffer"; "java.util.Hashtable"; "java.util.Vector"] in let targets = ["java.lang.StringBuffer"; "java.util.Hashtable"; "java.util.Vector"] in
@ -21,7 +20,7 @@ let is_synchronized_library_call =
false false
let should_skip_analysis = ConcurrencyModels.matcher_of_json Config.starvation_skip_analysis let should_skip_analysis = MethodMatcher.of_json Config.starvation_skip_analysis
(** magical value from https://developer.android.com/topic/performance/vitals/anr *) (** magical value from https://developer.android.com/topic/performance/vitals/anr *)
let android_anr_time_limit = 5.0 let android_anr_time_limit = 5.0
@ -90,16 +89,29 @@ let empty_or_excessive_timeout actuals =
false false
(* matchers used for normal analysis as well as in --dev-android-strict-mode *)
(* selection is a bit arbitrary as some would be generated anyway if not here; no harm though *) (* selection is a bit arbitrary as some would be generated anyway if not here; no harm though *)
(* some, like [file], need manual addition due to our lack of handling dynamic dispatch *) (* some, like [file], need manual addition due to our lack of handling dynamic dispatch *)
let strict_mode_common_matchers = let strict_mode_matcher =
let is_system_gc = let open MethodMatcher in
is_call_of_class "java.lang.System" ["gc"; "runFinalization"] |> Staged.unstage let open StarvationDomain.Event in
in (* NB [empty] searches superclasses too. Most of the classes below are final and we don't
let is_runtime_gc = is_call_of_class "java.lang.Runtime" ["gc"] |> Staged.unstage in really want to search superclasses for those that aren't, so for performance, disable that *)
let is_file_io = let empty = {empty with search_superclasses= Some false} in
is_call_of_class "java.io.File" let matcher_records =
[ { empty with
classname= "dalvik.system.BlockGuard$Policy"; methods= ["on"]; method_prefix= Some true }
; {empty with classname= "java.lang.System"; methods= ["gc"; "runFinalization"]}
; {empty with classname= "java.lang.Runtime"; methods= ["gc"]}
; {empty with classname= "java.net.Socket"; methods= ["connect"]}
(* all public constructors of Socket with two or more arguments call connect *)
; { empty with
classname= "java.net.Socket"
; methods= [Typ.Procname.Java.constructor_method_name]
; actuals_pred= Some (function [] | [_] -> false | _ -> true) }
; {empty with classname= "java.net.DatagramSocket"; methods= ["connect"]}
; { empty with
classname= "java.io.File"
; methods=
[ "canRead" [ "canRead"
; "canWrite" ; "canWrite"
; "createNewFile" ; "createNewFile"
@ -122,93 +134,62 @@ let strict_mode_common_matchers =
; "setLastModified" ; "setLastModified"
; "setReadable" ; "setReadable"
; "setReadOnly" ; "setReadOnly"
; "setWritable" ] ; "setWritable" ] } ]
|> Staged.unstage
in in
let is_socket_connect = is_call_of_class "java.net.Socket" ["connect"] |> Staged.unstage in let matcher =
let is_connected_socket_constructor = of_list (StrictModeModels.is_strict_mode_violation :: List.map matcher_records ~f:of_record)
(* all public constructors of Socket with two or more arguments call connect *)
let actuals_pred = function [] | [_] -> false | _ -> true in
is_call_of_class ~actuals_pred "java.net.Socket" [Typ.Procname.Java.constructor_method_name]
|> Staged.unstage
in
let is_datagram_socket_connect =
is_call_of_class "java.net.DatagramSocket" ["connect"] |> Staged.unstage
in in
let open StarvationDomain.Event in (matcher, High)
[ (is_connected_socket_constructor, High)
; (is_datagram_socket_connect, High)
; (is_file_io, High)
; (is_runtime_gc, High)
; (is_socket_connect, High)
; (is_system_gc, High) ]
let strict_mode_seed_matchers =
(* matcher for strict mode throws in Android libcore implementation,
used with --dev-android strict mode *)
let is_blockguard_on =
is_call_of_class ~method_prefix:true "dalvik.system.BlockGuard$Policy" ["on"] |> Staged.unstage
in
(is_blockguard_on, StarvationDomain.Event.High) :: strict_mode_common_matchers
let strict_mode_matchers =
let open StarvationDomain.Event in
(StrictModeModels.is_strict_mode_violation, High) :: strict_mode_common_matchers
let standard_matchers = let standard_matchers =
(* is the method called Object.wait or on subclass, without timeout or with excessive timeout ? *) let open MethodMatcher in
let is_object_wait = let open StarvationDomain.Event in
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "java.lang.Object" ["wait"] let high_sev =
|> Staged.unstage [ {empty with classname= "java.lang.Thread"; methods= ["sleep"]}
in ; { empty with
(* is the method called CountDownLath.await or on subclass? *) classname= "java.lang.Object"
let is_countdownlatch_await = ; methods= ["wait"]
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "java.util.concurrent.CountDownLatch" ; actuals_pred= Some empty_or_excessive_timeout }
["await"] ; { empty with
|> Staged.unstage classname= "java.util.concurrent.CountDownLatch"
in ; methods= ["await"]
; actuals_pred= Some empty_or_excessive_timeout }
(* an IBinder.transact call is an RPC. If the 4th argument (5th counting `this` as the first) (* an IBinder.transact call is an RPC. If the 4th argument (5th counting `this` as the first)
is int-zero then a reply is expected and returned from the remote process, thus potentially is int-zero then a reply is expected and returned from the remote process, thus potentially
blocking. If the 4th argument is anything else, we assume a one-way call which doesn't block. *) blocking. If the 4th argument is anything else, we assume a one-way call which doesn't block. *)
let is_two_way_binder_transact = ; { empty with
let actuals_pred actuals = classname= "android.os.IBinder"
List.nth actuals 4 |> Option.value_map ~default:false ~f:HilExp.is_int_zero ; methods= ["transact"]
in ; actuals_pred=
is_call_of_class ~actuals_pred "android.os.IBinder" ["transact"] |> Staged.unstage Some
in (fun actuals ->
let is_future_get = List.nth actuals 4 |> Option.value_map ~default:false ~f:HilExp.is_int_zero ) }
is_call_of_class ~search_superclasses:false ~actuals_pred:empty_or_excessive_timeout ; { empty with
"java.util.concurrent.Future" ["get"] classname= "android.accounts.AccountManager"
|> Staged.unstage ; methods= ["setUserData"]
in ; search_superclasses= Some false } ]
let is_accountManager_setUserData = in
is_call_of_class ~search_superclasses:false "android.accounts.AccountManager" ["setUserData"] let low_sev =
|> Staged.unstage [ { empty with
in classname= "java.util.concurrent.Future"
let is_asyncTask_get = ; methods= ["get"]
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "android.os.AsyncTask" ["get"] ; actuals_pred= Some empty_or_excessive_timeout
|> Staged.unstage ; search_superclasses= Some false }
in ; { empty with
(* consider any call to sleep as bad, even with timeouts lower than the anr limit *) classname= "android.os.AsyncTask"
let is_thread_sleep = is_call_of_class "java.lang.Thread" ["sleep"] |> Staged.unstage in ; methods= ["get"]
let open StarvationDomain.Event in ; actuals_pred= Some empty_or_excessive_timeout } ]
[ (is_accountManager_setUserData, High) in
; (is_two_way_binder_transact, High) let high_sev_matcher = List.map high_sev ~f:of_record |> of_list in
; (is_countdownlatch_await, High) let low_sev_matcher = List.map low_sev ~f:of_record |> of_list in
; (is_thread_sleep, High) [(high_sev_matcher, High); (low_sev_matcher, Low)]
; (is_object_wait, High)
; (is_asyncTask_get, Low)
; (is_future_get, Low) ]
(* sort from High to Low *) (* sort from High to Low *)
let may_block = let may_block =
let matchers = let matchers =
if Config.dev_android_strict_mode then strict_mode_seed_matchers if Config.starvation_strict_mode then strict_mode_matcher :: standard_matchers
else if Config.starvation_strict_mode then strict_mode_matchers @ standard_matchers
else standard_matchers else standard_matchers
in in
fun tenv pn actuals -> fun tenv pn actuals ->

@ -6,11 +6,11 @@
*) *)
open! IStd open! IStd
open ConcurrencyModels open MethodMatcher
(* frameworks/base/core/java/android/app/backup/BackupAgent.java *) (* frameworks/base/core/java/android/app/backup/BackupAgent.java *)
let is_BackupAgent_method = let is_BackupAgent_method =
is_call_of_class "android.app.backup.BackupAgent" call_matches "android.app.backup.BackupAgent"
["onRestoreFile" (* onRestoreFile(ParcelFileDescriptor,long,int,String,String,long,long) *) ["onRestoreFile" (* onRestoreFile(ParcelFileDescriptor,long,int,String,String,long,long) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -18,14 +18,14 @@ let is_BackupAgent_method =
(* frameworks/base/core/java/android/app/DownloadManager.java *) (* frameworks/base/core/java/android/app/DownloadManager.java *)
let is_DownloadManager_method = let is_DownloadManager_method =
is_call_of_class "android.app.DownloadManager" ["rename" (* rename(Context,long,String) *) call_matches "android.app.DownloadManager" ["rename" (* rename(Context,long,String) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/app/NotificationManager.java *) (* frameworks/base/core/java/android/app/NotificationManager.java *)
let is_NotificationManager_method = let is_NotificationManager_method =
is_call_of_class "android.app.NotificationManager" call_matches "android.app.NotificationManager"
["notifyAsUser" (* notifyAsUser(String,int,Notification,UserHandle) *) ["notifyAsUser" (* notifyAsUser(String,int,Notification,UserHandle) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -33,14 +33,14 @@ let is_NotificationManager_method =
(* frameworks/base/core/java/android/content/pm/ActivityInfo.java *) (* frameworks/base/core/java/android/content/pm/ActivityInfo.java *)
let is_ActivityInfo_method = let is_ActivityInfo_method =
is_call_of_class "android.content.pm.ActivityInfo" ["dump" (* dump(Printer,String,int) *) call_matches "android.content.pm.ActivityInfo" ["dump" (* dump(Printer,String,int) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ApplicationInfo.java *) (* frameworks/base/core/java/android/content/pm/ApplicationInfo.java *)
let is_ApplicationInfo_method = let is_ApplicationInfo_method =
is_call_of_class "android.content.pm.ApplicationInfo" call_matches "android.content.pm.ApplicationInfo"
[ "dump" [ "dump"
; (* dump(Printer,String,int) *) ; (* dump(Printer,String,int) *)
"getHiddenApiEnforcementPolicy" "getHiddenApiEnforcementPolicy"
@ -53,28 +53,28 @@ let is_ApplicationInfo_method =
(* frameworks/base/core/java/android/content/pm/ProviderInfo.java *) (* frameworks/base/core/java/android/content/pm/ProviderInfo.java *)
let is_ProviderInfo_method = let is_ProviderInfo_method =
is_call_of_class "android.content.pm.ProviderInfo" ["dump" (* dump(Printer,String,int) *) call_matches "android.content.pm.ProviderInfo" ["dump" (* dump(Printer,String,int) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ResolveInfo.java *) (* frameworks/base/core/java/android/content/pm/ResolveInfo.java *)
let is_ResolveInfo_method = let is_ResolveInfo_method =
is_call_of_class "android.content.pm.ResolveInfo" ["dump" (* dump(Printer,String,int) *) call_matches "android.content.pm.ResolveInfo" ["dump" (* dump(Printer,String,int) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ServiceInfo.java *) (* frameworks/base/core/java/android/content/pm/ServiceInfo.java *)
let is_ServiceInfo_method = let is_ServiceInfo_method =
is_call_of_class "android.content.pm.ServiceInfo" ["dump" (* dump(Printer,String,int) *) call_matches "android.content.pm.ServiceInfo" ["dump" (* dump(Printer,String,int) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java *) (* frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java *)
let is_SQLiteDatabase_method = let is_SQLiteDatabase_method =
is_call_of_class "android.database.sqlite.SQLiteDatabase" call_matches "android.database.sqlite.SQLiteDatabase"
[ "addCustomFunction" [ "addCustomFunction"
; (* addCustomFunction(String,int,SQLiteDatabase$CustomFunction) *) ; (* addCustomFunction(String,int,SQLiteDatabase$CustomFunction) *)
"reopenReadWrite" "reopenReadWrite"
@ -85,20 +85,20 @@ let is_SQLiteDatabase_method =
(* frameworks/base/core/java/android/ddm/DdmHandleHeap.java *) (* frameworks/base/core/java/android/ddm/DdmHandleHeap.java *)
let is_DdmHandleHeap_method = let is_DdmHandleHeap_method =
is_call_of_class "android.ddm.DdmHandleHeap" ["handleChunk" (* handleChunk(Chunk) *) call_matches "android.ddm.DdmHandleHeap" ["handleChunk" (* handleChunk(Chunk) *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/net/Uri.java *) (* frameworks/base/core/java/android/net/Uri.java *)
let is_Uri_method = let is_Uri_method =
is_call_of_class "android.net.Uri" ["getCanonicalUri" (* getCanonicalUri() *) call_matches "android.net.Uri" ["getCanonicalUri" (* getCanonicalUri() *)
] |> Staged.unstage ] |> Staged.unstage
(* frameworks/base/core/java/android/os/Environment.java *) (* frameworks/base/core/java/android/os/Environment.java *)
let is_Environment_method = let is_Environment_method =
is_call_of_class "android.os.Environment" call_matches "android.os.Environment"
["classifyExternalStorageDirectory" (* classifyExternalStorageDirectory(File) *) ["classifyExternalStorageDirectory" (* classifyExternalStorageDirectory(File) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -106,14 +106,14 @@ let is_Environment_method =
(* frameworks/base/core/java/android/os/Parcel.java *) (* frameworks/base/core/java/android/os/Parcel.java *)
let is_Parcel_method = let is_Parcel_method =
is_call_of_class "android.os.Parcel" ["readExceptionCode" (* readExceptionCode() *) call_matches "android.os.Parcel" ["readExceptionCode" (* readExceptionCode() *)
] ]
|> Staged.unstage |> Staged.unstage
(* frameworks/base/core/java/android/os/RecoverySystem.java *) (* frameworks/base/core/java/android/os/RecoverySystem.java *)
let is_RecoverySystem_method = let is_RecoverySystem_method =
is_call_of_class "android.os.RecoverySystem" call_matches "android.os.RecoverySystem"
[ "handleAftermath" [ "handleAftermath"
; (* handleAftermath(Context) *) ; (* handleAftermath(Context) *)
"rebootPromptAndWipeUserData" "rebootPromptAndWipeUserData"
@ -134,7 +134,7 @@ let is_RecoverySystem_method =
(* frameworks/base/core/java/android/os/storage/StorageManager.java *) (* frameworks/base/core/java/android/os/storage/StorageManager.java *)
let is_StorageManager_method = let is_StorageManager_method =
is_call_of_class "android.os.storage.StorageManager" call_matches "android.os.storage.StorageManager"
[ "getPrimaryStoragePathAndSize" [ "getPrimaryStoragePathAndSize"
; (* getPrimaryStoragePathAndSize() *) ; (* getPrimaryStoragePathAndSize() *)
"getPrimaryStorageSize" "getPrimaryStorageSize"
@ -151,7 +151,7 @@ let is_StorageManager_method =
(* frameworks/base/core/java/android/os/StrictMode.java *) (* frameworks/base/core/java/android/os/StrictMode.java *)
let is_StrictMode_method = let is_StrictMode_method =
is_call_of_class "android.os.StrictMode" call_matches "android.os.StrictMode"
[ "conditionallyCheckInstanceCounts" [ "conditionallyCheckInstanceCounts"
; (* conditionallyCheckInstanceCounts() *) ; (* conditionallyCheckInstanceCounts() *)
"decrementExpectedActivityCount" "decrementExpectedActivityCount"
@ -172,7 +172,7 @@ let is_StrictMode_method =
(* frameworks/base/core/java/android/util/AtomicFile.java *) (* frameworks/base/core/java/android/util/AtomicFile.java *)
let is_AtomicFile_method = let is_AtomicFile_method =
is_call_of_class "android.util.AtomicFile" call_matches "android.util.AtomicFile"
["getLastModifiedTime"; (* getLastModifiedTime() *) "startWrite" (* startWrite(long) *) ["getLastModifiedTime"; (* getLastModifiedTime() *) "startWrite" (* startWrite(long) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -180,7 +180,7 @@ let is_AtomicFile_method =
(* frameworks/base/core/java/android/webkit/WebViewFactory.java *) (* frameworks/base/core/java/android/webkit/WebViewFactory.java *)
let is_WebViewFactory_method = let is_WebViewFactory_method =
is_call_of_class "android.webkit.WebViewFactory" call_matches "android.webkit.WebViewFactory"
["onWebViewProviderChanged" (* onWebViewProviderChanged(PackageInfo) *) ["onWebViewProviderChanged" (* onWebViewProviderChanged(PackageInfo) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -188,7 +188,7 @@ let is_WebViewFactory_method =
(* frameworks/base/core/java/android/webkit/WebViewLibraryLoader.java *) (* frameworks/base/core/java/android/webkit/WebViewLibraryLoader.java *)
let is_WebViewLibraryLoader_method = let is_WebViewLibraryLoader_method =
is_call_of_class "android.webkit.WebViewLibraryLoader" call_matches "android.webkit.WebViewLibraryLoader"
["getWebViewNativeLibrary" (* getWebViewNativeLibrary(PackageInfo,boolean) *) ["getWebViewNativeLibrary" (* getWebViewNativeLibrary(PackageInfo,boolean) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -196,7 +196,7 @@ let is_WebViewLibraryLoader_method =
(* frameworks/base/media/java/android/media/MiniThumbFile.java *) (* frameworks/base/media/java/android/media/MiniThumbFile.java *)
let is_MiniThumbFile_method = let is_MiniThumbFile_method =
is_call_of_class "android.media.MiniThumbFile" call_matches "android.media.MiniThumbFile"
[ "eraseMiniThumb" [ "eraseMiniThumb"
; (* eraseMiniThumb(long) *) ; (* eraseMiniThumb(long) *)
"getMagic" "getMagic"
@ -211,7 +211,7 @@ let is_MiniThumbFile_method =
(* frameworks/base/media/java/android/media/RingtoneManager.java *) (* frameworks/base/media/java/android/media/RingtoneManager.java *)
let is_RingtoneManager_method = let is_RingtoneManager_method =
is_call_of_class "android.media.RingtoneManager" call_matches "android.media.RingtoneManager"
["deleteExternalRingtone" (* deleteExternalRingtone(Uri) *) ["deleteExternalRingtone" (* deleteExternalRingtone(Uri) *)
] ]
|> Staged.unstage |> Staged.unstage
@ -219,7 +219,7 @@ let is_RingtoneManager_method =
(* frameworks/multidex/library/src/androidx/multidex/MultiDex.java *) (* frameworks/multidex/library/src/androidx/multidex/MultiDex.java *)
let is_MultiDex_method = let is_MultiDex_method =
is_call_of_class "androidx.multidex.MultiDex" call_matches "androidx.multidex.MultiDex"
[ "install" [ "install"
; (* install(Context) *) ; (* install(Context) *)
"installInstrumentation" "installInstrumentation"
@ -230,7 +230,7 @@ let is_MultiDex_method =
(* libcore/ojluni/src/main/java/java/util/logging/FileHandler.java *) (* libcore/ojluni/src/main/java/java/util/logging/FileHandler.java *)
let is_FileHandler_method = let is_FileHandler_method =
is_call_of_class "java.util.logging.FileHandler" ["run" (* run() *) call_matches "java.util.logging.FileHandler" ["run" (* run() *)
] |> Staged.unstage ] |> Staged.unstage

@ -286,20 +286,10 @@ let should_report_deadlock_on_current_proc current_elem endpoint_elem =
< 0 < 0
let public_package_prefixes = ["java"; "android"]
let should_report pdesc = let should_report pdesc =
match Procdesc.get_proc_name pdesc with match Procdesc.get_proc_name pdesc with
| Typ.Procname.Java java_pname -> | Typ.Procname.Java java_pname ->
( if Config.dev_android_strict_mode then Procdesc.get_access pdesc <> PredSymb.Private
match Typ.Procname.Java.get_package java_pname with
| None ->
false
| Some package ->
(* the proc must be package-public, but we can't check for that *)
List.exists public_package_prefixes ~f:(fun prefix -> String.is_prefix package ~prefix)
else true )
&& Procdesc.get_access pdesc <> PredSymb.Private
&& (not (Typ.Procname.Java.is_autogen_method java_pname)) && (not (Typ.Procname.Java.is_autogen_method java_pname))
&& not (Typ.Procname.Java.is_class_initializer java_pname) && not (Typ.Procname.Java.is_class_initializer java_pname)
| _ -> | _ ->
@ -331,8 +321,6 @@ let fold_reportable_summaries (tenv, current_pdesc) clazz ~init ~f =
The net effect of the above issues is that we will only see these locks in conflicting pairs The net effect of the above issues is that we will only see these locks in conflicting pairs
once, as opposed to twice with all other deadlock pairs. *) once, as opposed to twice with all other deadlock pairs. *)
let report_deadlocks env {StarvationDomain.order; ui} report_map' = let report_deadlocks env {StarvationDomain.order; ui} report_map' =
if Config.dev_android_strict_mode then ReportMap.empty
else
let open StarvationDomain in let open StarvationDomain in
let tenv, current_pdesc = env in let tenv, current_pdesc = env in
let current_pname = Procdesc.get_proc_name current_pdesc in let current_pname = Procdesc.get_proc_name current_pdesc in
@ -460,22 +448,6 @@ let report_starvation env {StarvationDomain.events; ui} report_map' =
order acc order acc
else acc ) ) else acc ) )
in in
let report_strict_mode event rmap =
match event.Event.elem with
| MayBlock (_, sev) ->
let error_message =
Format.asprintf "Method %a commits a strict mode violation; %a."
(MF.wrap_monospaced Typ.Procname.pp)
current_pname Event.pp event
in
let loc = Event.get_loc event in
let ltr = Event.make_trace current_pname event in
ReportMap.add_starvation tenv sev current_pdesc loc ltr error_message rmap
| _ ->
rmap
in
if Config.dev_android_strict_mode then EventDomain.fold report_strict_mode events report_map'
else
match ui with match ui with
| AbstractDomain.Types.Bottom -> | AbstractDomain.Types.Bottom ->
report_map' report_map'

@ -1,13 +0,0 @@
# Copyright (c) 2018-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
TESTS_DIR = ../../..
ANALYZER = checkers
INFER_OPTIONS = --starvation-only --dev-android-strict-mode --debug-exceptions
INFERPRINT_OPTIONS = --issues-tests
SOURCES = dalvik/system/BlockGuard.java java/GuardTest.java $(wildcard *.java)
include $(TESTS_DIR)/javac.make

@ -1,30 +0,0 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import dalvik.system.BlockGuard;
public class NonPublicGuardTest {
public void testWriteOk() {
BlockGuard.getThreadPolicy().onWriteToDisk();
}
public void testReadOk() {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
public void testNetOk() {
BlockGuard.getThreadPolicy().onNetwork();
}
private void privateOk() {
testReadOk();
}
public void intraprocOk() {
privateOk();
}
}

@ -1,30 +0,0 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package dalvik.system;
public final class BlockGuard {
public interface Policy {
void onWriteToDisk();
void onReadFromDisk();
void onNetwork();
int getPolicyMask();
}
public static final Policy threadPolicy = new Policy() {
public void onWriteToDisk() {}
public void onReadFromDisk() {}
public void onNetwork() {}
public int getPolicyMask() {
return 0;
}
};
public static Policy getThreadPolicy() {
return threadPolicy;
}
}

@ -1,4 +0,0 @@
codetoanalyze/java/devstrictmode/java/GuardTest.java, java.GuardTest.intraprocBad():void, 29, STARVATION, no_bucket, ERROR, [`void GuardTest.intraprocBad()`,Method call: `void GuardTest.privateOk()`,Method call: `void GuardTest.testReadBad()`,calls `void BlockGuard$Policy.onReadFromDisk()` from `void GuardTest.testReadBad()`]
codetoanalyze/java/devstrictmode/java/GuardTest.java, java.GuardTest.testNetBad():void, 21, STARVATION, no_bucket, ERROR, [`void GuardTest.testNetBad()`,calls `void BlockGuard$Policy.onNetwork()` from `void GuardTest.testNetBad()`]
codetoanalyze/java/devstrictmode/java/GuardTest.java, java.GuardTest.testReadBad():void, 17, STARVATION, no_bucket, ERROR, [`void GuardTest.testReadBad()`,calls `void BlockGuard$Policy.onReadFromDisk()` from `void GuardTest.testReadBad()`]
codetoanalyze/java/devstrictmode/java/GuardTest.java, java.GuardTest.testWriteBad():void, 13, STARVATION, no_bucket, ERROR, [`void GuardTest.testWriteBad()`,calls `void BlockGuard$Policy.onWriteToDisk()` from `void GuardTest.testWriteBad()`]

@ -1,32 +0,0 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package java;
import dalvik.system.BlockGuard;
public class GuardTest {
public void testWriteBad() {
BlockGuard.getThreadPolicy().onWriteToDisk();
}
public void testReadBad() {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
public void testNetBad() {
BlockGuard.getThreadPolicy().onNetwork();
}
private void privateOk() {
testReadBad();
}
public void intraprocBad() {
privateOk();
}
}
Loading…
Cancel
Save