[starvation] strict mode meta-analyzer for Android

Summary:
First step in writing an analyzer that is meant to run only on Android core library implementation.
This will, when finished, compute the library entrypoints that may lead to a strict mode violation.
The normal analyzer will use those to statically flag strict mode violations in app code.

Strict Mode is an Android debug mode, where doing certain things (like disk read/write or network activity) on the UI thread will raise an exception.  We want to statically catch these, as well as indirect versions (the UI thread takes a lock and another thread holding that lock calls a method that would be a strict mode violation).

Reviewed By: mbouaziz

Differential Revision: D9634407

fbshipit-source-id: c30bcedb3
master
Nikos Gorogiannis 6 years ago committed by Facebook Github Bot
parent 3aab371b1f
commit 96e698a458

@ -92,7 +92,8 @@ BUILD_SYSTEMS_TESTS += \
DIRECT_TESTS += \
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_strictmode
ifneq ($(ANT),no)
BUILD_SYSTEMS_TESTS += ant
endif

@ -2096,6 +2096,12 @@ and stats_report =
"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 =
CLOpt.mk_bool ~deprecated:["subtype_multirange"] ~long:"subtype-multirange" ~default:true
"Use the multirange subtyping domain"
@ -2597,6 +2603,8 @@ and default_linters = !default_linters
and dependency_mode = !dependencies
and dev_android_strict_mode = !dev_android_strict_mode
and developer_mode = !developer_mode
and differential_filter_files = !differential_filter_files

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

@ -220,23 +220,32 @@ let runs_on_ui_thread tenv proc_desc =
let is_call_of_class ?(search_superclasses = true) ?(method_prefix = false)
?(actuals_pred = fun _ -> true) class_names method_name =
let is_target_class =
let target_set = List.map class_names ~f:Typ.Name.Java.from_string |> Typ.Name.Set.of_list in
fun tname -> Typ.Name.Set.mem tname target_set
?(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 is_target_struct tname _ = is_target_class tname in
Staged.stage (fun tenv pn actuals ->
actuals_pred actuals
&&
match pn with
| Typ.Procname.Java java_pname ->
let classname = Typ.Procname.Java.get_class_type_name java_pname in
let mthd = Typ.Procname.Java.get_method java_pname in
( if method_prefix then String.is_prefix mthd ~prefix:method_name
else String.equal mthd method_name )
&&
if search_superclasses then PatternMatch.supertype_exists tenv is_target_struct classname
else is_target_class classname
| _ ->
false )
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

@ -38,7 +38,7 @@ val find_annotated_or_overriden_annotated_method :
val is_call_of_class :
?search_superclasses:bool
-> ?method_prefix:bool
-> ?actuals_pred:('a -> bool)
-> string sexp_list
-> ?actuals_pred:(HilExp.t list -> bool)
-> string
-> (Tenv.t -> BuiltinDecl.t -> 'a -> bool) Staged.t
-> string list
-> (Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool) Staged.t

@ -22,7 +22,7 @@ let is_synchronized_library_call =
let is_futures_getdone =
is_call_of_class ["com.google.common.util.concurrent.Futures"] "getDone" |> Staged.unstage
is_call_of_class "com.google.common.util.concurrent.Futures" ["getDone"] |> Staged.unstage
let should_skip_analysis =
@ -99,15 +99,14 @@ let empty_or_excessive_timeout actuals =
(** is the method called Object.wait or on subclass, without timeout or with excessive timeout ? *)
let is_object_wait =
is_call_of_class ~actuals_pred:empty_or_excessive_timeout ["java.lang.Object"] "wait"
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "java.lang.Object" ["wait"]
|> Staged.unstage
(** is the method called CountDownLath.await or on subclass? *)
let is_countdownlatch_await =
is_call_of_class ~actuals_pred:empty_or_excessive_timeout
["java.util.concurrent.CountDownLatch"]
"await"
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "java.util.concurrent.CountDownLatch"
["await"]
|> Staged.unstage
@ -119,40 +118,116 @@ let is_two_way_binder_transact =
let actuals_pred actuals =
List.nth actuals 4 |> Option.value_map ~default:false ~f:HilExp.is_int_zero
in
is_call_of_class ~actuals_pred ["android.os.IBinder"] "transact" |> Staged.unstage
is_call_of_class ~actuals_pred "android.os.IBinder" ["transact"] |> Staged.unstage
(** is it a call to Future.get()? *)
let is_future_get =
is_call_of_class ~search_superclasses:false ~actuals_pred:empty_or_excessive_timeout
["java.util.concurrent.Future"] "get"
"java.util.concurrent.Future" ["get"]
|> Staged.unstage
let is_accountManager_setUserData =
is_call_of_class ~search_superclasses:false ["android.accounts.AccountManager"] "setUserData"
is_call_of_class ~search_superclasses:false "android.accounts.AccountManager" ["setUserData"]
|> Staged.unstage
let is_asyncTask_get =
is_call_of_class ~actuals_pred:empty_or_excessive_timeout ["android.os.AsyncTask"] "get"
is_call_of_class ~actuals_pred:empty_or_excessive_timeout "android.os.AsyncTask" ["get"]
|> Staged.unstage
(* consider any call to sleep as bad, even with timeouts lower than the anr limit *)
let is_thread_sleep = is_call_of_class ["java.lang.Thread"] "sleep" |> Staged.unstage
let is_thread_sleep = is_call_of_class "java.lang.Thread" ["sleep"] |> Staged.unstage
(* 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
let is_system_gc = is_call_of_class "java.lang.System" ["gc"; "runFinalization"] |> Staged.unstage
let is_runtime_gc = is_call_of_class "java.lang.Runtime" ["gc"] |> Staged.unstage
let is_file_io =
is_call_of_class "java.io.File"
[ "canRead"
; "canWrite"
; "createNewFile"
; "createTempFile"
; "delete"
; "getCanonicalPath"
; "getFreeSpace"
; "getTotalSpace"
; "getUsableSpace"
; "isDirectory"
; "isFile"
; "isHidden"
; "lastModified"
; "length"
; "list"
; "listFiles"
; "mkdir"
; "renameTo"
; "setExecutable"
; "setLastModified"
; "setReadable"
; "setReadOnly"
; "setWritable" ]
|> Staged.unstage
let is_socket_connect = is_call_of_class "java.net.Socket" ["connect"] |> Staged.unstage
let is_connected_socket_constructor =
(* 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
let is_datagram_socket_connect =
is_call_of_class "java.net.DatagramSocket" ["connect"] |> Staged.unstage
(* 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 *)
(* some, like [file], need manual addition due to our lack of handling dynamic dispatch *)
let strict_mode_common_matchers =
let open StarvationDomain.Event in
[ (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 =
(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
(* at most one function is allowed to be true, sort from High to Low *)
let may_block =
let open StarvationDomain.Event in
let matchers =
[ (is_accountManager_setUserData, High)
; (is_two_way_binder_transact, High)
; (is_countdownlatch_await, High)
; (is_thread_sleep, High)
; (is_object_wait, High)
; (is_asyncTask_get, Low)
; (is_future_get, Low) ]
if Config.dev_android_strict_mode then strict_mode_seed_matchers
else
strict_mode_matchers
@ [ (is_accountManager_setUserData, High)
; (is_two_way_binder_transact, High)
; (is_countdownlatch_await, High)
; (is_thread_sleep, High)
; (is_object_wait, High)
; (is_asyncTask_get, Low)
; (is_future_get, Low) ]
in
fun tenv pn actuals ->
List.find_map matchers ~f:(fun (matcher, sev) -> Option.some_if (matcher tenv pn actuals) sev)

@ -0,0 +1,263 @@
(*
* 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.
*)
open! IStd
open ConcurrencyModels
(* frameworks/base/core/java/android/app/backup/BackupAgent.java *)
let is_BackupAgent_method =
is_call_of_class "android.app.backup.BackupAgent"
["onRestoreFile" (* onRestoreFile(ParcelFileDescriptor,long,int,String,String,long,long) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/app/DownloadManager.java *)
let is_DownloadManager_method =
is_call_of_class "android.app.DownloadManager" ["rename" (* rename(Context,long,String) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/app/NotificationManager.java *)
let is_NotificationManager_method =
is_call_of_class "android.app.NotificationManager"
["notifyAsUser" (* notifyAsUser(String,int,Notification,UserHandle) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ActivityInfo.java *)
let is_ActivityInfo_method =
is_call_of_class "android.content.pm.ActivityInfo" ["dump" (* dump(Printer,String,int) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ApplicationInfo.java *)
let is_ApplicationInfo_method =
is_call_of_class "android.content.pm.ApplicationInfo"
[ "dump"
; (* dump(Printer,String,int) *)
"getHiddenApiEnforcementPolicy"
; (* getHiddenApiEnforcementPolicy() *)
"maybeUpdateHiddenApiEnforcementPolicy"
(* maybeUpdateHiddenApiEnforcementPolicy(int,int) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ProviderInfo.java *)
let is_ProviderInfo_method =
is_call_of_class "android.content.pm.ProviderInfo" ["dump" (* dump(Printer,String,int) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ResolveInfo.java *)
let is_ResolveInfo_method =
is_call_of_class "android.content.pm.ResolveInfo" ["dump" (* dump(Printer,String,int) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/content/pm/ServiceInfo.java *)
let is_ServiceInfo_method =
is_call_of_class "android.content.pm.ServiceInfo" ["dump" (* dump(Printer,String,int) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java *)
let is_SQLiteDatabase_method =
is_call_of_class "android.database.sqlite.SQLiteDatabase"
[ "addCustomFunction"
; (* addCustomFunction(String,int,SQLiteDatabase$CustomFunction) *)
"reopenReadWrite"
(* reopenReadWrite() *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/ddm/DdmHandleHeap.java *)
let is_DdmHandleHeap_method =
is_call_of_class "android.ddm.DdmHandleHeap" ["handleChunk" (* handleChunk(Chunk) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/net/Uri.java *)
let is_Uri_method =
is_call_of_class "android.net.Uri" ["getCanonicalUri" (* getCanonicalUri() *)
] |> Staged.unstage
(* frameworks/base/core/java/android/os/Environment.java *)
let is_Environment_method =
is_call_of_class "android.os.Environment"
["classifyExternalStorageDirectory" (* classifyExternalStorageDirectory(File) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/os/Parcel.java *)
let is_Parcel_method =
is_call_of_class "android.os.Parcel" ["readExceptionCode" (* readExceptionCode() *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/os/RecoverySystem.java *)
let is_RecoverySystem_method =
is_call_of_class "android.os.RecoverySystem"
[ "handleAftermath"
; (* handleAftermath(Context) *)
"rebootPromptAndWipeUserData"
; (* rebootPromptAndWipeUserData(Context,String) *)
"rebootWipeCache"
; (* rebootWipeCache(Context,String) *)
"rebootWipeUserData"
; (* rebootWipeUserData(Context,boolean) *)
"rebootWipeUserData"
; (* rebootWipeUserData(Context,boolean,String,boolean) *)
"rebootWipeUserData"
; (* rebootWipeUserData(Context,boolean,String,boolean,boolean) *)
"rebootWipeUserData"
(* rebootWipeUserData(Context,String) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/os/storage/StorageManager.java *)
let is_StorageManager_method =
is_call_of_class "android.os.storage.StorageManager"
[ "getPrimaryStoragePathAndSize"
; (* getPrimaryStoragePathAndSize() *)
"getPrimaryStorageSize"
; (* getPrimaryStorageSize() *)
"getStorageBytesUntilLow"
; (* getStorageBytesUntilLow(File) *)
"getStorageCacheBytes"
; (* getStorageCacheBytes(File,int) *)
"getStorageLowBytes"
(* getStorageLowBytes(File) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/os/StrictMode.java *)
let is_StrictMode_method =
is_call_of_class "android.os.StrictMode"
[ "conditionallyCheckInstanceCounts"
; (* conditionallyCheckInstanceCounts() *)
"decrementExpectedActivityCount"
; (* decrementExpectedActivityCount(Class) *)
"noteDiskRead"
; (* noteDiskRead() *)
"noteDiskWrite"
; (* noteDiskWrite() *)
"noteResourceMismatch"
; (* noteResourceMismatch(Object) *)
"noteUnbufferedIO"
; (* noteUnbufferedIO() *)
"queueIdle"
(* queueIdle() *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/util/AtomicFile.java *)
let is_AtomicFile_method =
is_call_of_class "android.util.AtomicFile"
["getLastModifiedTime"; (* getLastModifiedTime() *) "startWrite" (* startWrite(long) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/webkit/WebViewFactory.java *)
let is_WebViewFactory_method =
is_call_of_class "android.webkit.WebViewFactory"
["onWebViewProviderChanged" (* onWebViewProviderChanged(PackageInfo) *)
]
|> Staged.unstage
(* frameworks/base/core/java/android/webkit/WebViewLibraryLoader.java *)
let is_WebViewLibraryLoader_method =
is_call_of_class "android.webkit.WebViewLibraryLoader"
["getWebViewNativeLibrary" (* getWebViewNativeLibrary(PackageInfo,boolean) *)
]
|> Staged.unstage
(* frameworks/base/media/java/android/media/MiniThumbFile.java *)
let is_MiniThumbFile_method =
is_call_of_class "android.media.MiniThumbFile"
[ "eraseMiniThumb"
; (* eraseMiniThumb(long) *)
"getMagic"
; (* getMagic(long) *)
"getMiniThumbFromFile"
; (* getMiniThumbFromFile(long,byte[]) *)
"saveMiniThumbToFile"
(* saveMiniThumbToFile(byte[],long,long) *)
]
|> Staged.unstage
(* frameworks/base/media/java/android/media/RingtoneManager.java *)
let is_RingtoneManager_method =
is_call_of_class "android.media.RingtoneManager"
["deleteExternalRingtone" (* deleteExternalRingtone(Uri) *)
]
|> Staged.unstage
(* frameworks/multidex/library/src/androidx/multidex/MultiDex.java *)
let is_MultiDex_method =
is_call_of_class "androidx.multidex.MultiDex"
[ "install"
; (* install(Context) *)
"installInstrumentation"
(* installInstrumentation(Context,Context) *)
]
|> Staged.unstage
(* libcore/ojluni/src/main/java/java/util/logging/FileHandler.java *)
let is_FileHandler_method =
is_call_of_class "java.util.logging.FileHandler" ["run" (* run() *)
] |> Staged.unstage
let is_strict_mode_violation =
let matchers =
[ is_BackupAgent_method
; is_DownloadManager_method
; is_NotificationManager_method
; is_ActivityInfo_method
; is_ApplicationInfo_method
; is_ProviderInfo_method
; is_ResolveInfo_method
; is_ServiceInfo_method
; is_SQLiteDatabase_method
; is_DdmHandleHeap_method
; is_Uri_method
; is_Environment_method
; is_Parcel_method
; is_RecoverySystem_method
; is_StorageManager_method
; is_StrictMode_method
; is_AtomicFile_method
; is_WebViewFactory_method
; is_WebViewLibraryLoader_method
; is_MiniThumbFile_method
; is_RingtoneManager_method
; is_MultiDex_method
; is_FileHandler_method ]
in
fun tenv pn actuals -> List.exists matchers ~f:(fun m -> m tenv pn actuals)

@ -0,0 +1,11 @@
(*
* 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.
*)
open! IStd
val is_strict_mode_violation : Tenv.t -> Typ.Procname.t -> HilExp.t list -> bool
(** is the method a potential strict mode violation, given the actuals passed? *)

@ -286,10 +286,20 @@ let should_report_deadlock_on_current_proc current_elem endpoint_elem =
< 0
let public_package_prefixes = ["java"; "android"]
let should_report pdesc =
match Procdesc.get_proc_name pdesc with
| Typ.Procname.Java java_pname ->
Procdesc.get_access pdesc <> PredSymb.Private
( if Config.dev_android_strict_mode then
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_class_initializer java_pname)
| _ ->
@ -321,73 +331,75 @@ 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
once, as opposed to twice with all other deadlock pairs. *)
let report_deadlocks env {StarvationDomain.order; ui} report_map' =
let open StarvationDomain in
let tenv, current_pdesc = env in
let current_pname = Procdesc.get_proc_name current_pdesc in
let pp_acquire fmt (lock, loc, pname) =
F.fprintf fmt "%a (%a in %a)" Lock.pp lock Location.pp loc
(MF.wrap_monospaced Typ.Procname.pp)
pname
in
let pp_thread fmt
( pname
, { Order.loc= loc1
; trace= trace1
; elem= {first= lock1; eventually= {elem= event; loc= loc2; trace= trace2}} } ) =
match event with
| LockAcquire lock2 ->
let pname1 = List.last trace1 |> Option.value_map ~default:pname ~f:CallSite.pname in
let pname2 = List.last trace2 |> Option.value_map ~default:pname1 ~f:CallSite.pname in
F.fprintf fmt "first %a and then %a" pp_acquire (lock1, loc1, pname1) pp_acquire
(lock2, loc2, pname2)
| _ ->
L.die InternalError "Trying to report a deadlock without two lock events."
in
let report_endpoint_elem current_elem endpoint_pname elem report_map =
if
not
( Order.may_deadlock current_elem elem
&& should_report_deadlock_on_current_proc current_elem elem )
then report_map
else
let () = debug "Possible deadlock:@.%a@.%a@." Order.pp current_elem Order.pp elem in
match (current_elem.Order.elem.eventually.elem, elem.Order.elem.eventually.elem) with
| LockAcquire _, LockAcquire _ ->
let error_message =
Format.asprintf
"Potential deadlock.@.Trace 1 (starts at %a) %a.@.Trace 2 (starts at %a), %a."
(MF.wrap_monospaced Typ.Procname.pp)
current_pname pp_thread (current_pname, current_elem)
(MF.wrap_monospaced Typ.Procname.pp)
endpoint_pname pp_thread (endpoint_pname, elem)
in
let first_trace = Order.make_trace ~header:"[Trace 1] " current_pname current_elem in
let second_trace = Order.make_trace ~header:"[Trace 2] " endpoint_pname elem in
let ltr = first_trace @ second_trace in
let loc = Order.get_loc current_elem in
ReportMap.add_deadlock tenv current_pdesc loc ltr error_message report_map
| _, _ ->
if Config.dev_android_strict_mode then ReportMap.empty
else
let open StarvationDomain in
let tenv, current_pdesc = env in
let current_pname = Procdesc.get_proc_name current_pdesc in
let pp_acquire fmt (lock, loc, pname) =
F.fprintf fmt "%a (%a in %a)" Lock.pp lock Location.pp loc
(MF.wrap_monospaced Typ.Procname.pp)
pname
in
let pp_thread fmt
( pname
, { Order.loc= loc1
; trace= trace1
; elem= {first= lock1; eventually= {elem= event; loc= loc2; trace= trace2}} } ) =
match event with
| LockAcquire lock2 ->
let pname1 = List.last trace1 |> Option.value_map ~default:pname ~f:CallSite.pname in
let pname2 = List.last trace2 |> Option.value_map ~default:pname1 ~f:CallSite.pname in
F.fprintf fmt "first %a and then %a" pp_acquire (lock1, loc1, pname1) pp_acquire
(lock2, loc2, pname2)
| _ ->
L.die InternalError "Trying to report a deadlock without two lock events."
in
let report_endpoint_elem current_elem endpoint_pname elem report_map =
if
not
( Order.may_deadlock current_elem elem
&& should_report_deadlock_on_current_proc current_elem elem )
then report_map
else
let () = debug "Possible deadlock:@.%a@.%a@." Order.pp current_elem Order.pp elem in
match (current_elem.Order.elem.eventually.elem, elem.Order.elem.eventually.elem) with
| LockAcquire _, LockAcquire _ ->
let error_message =
Format.asprintf
"Potential deadlock.@.Trace 1 (starts at %a) %a.@.Trace 2 (starts at %a), %a."
(MF.wrap_monospaced Typ.Procname.pp)
current_pname pp_thread (current_pname, current_elem)
(MF.wrap_monospaced Typ.Procname.pp)
endpoint_pname pp_thread (endpoint_pname, elem)
in
let first_trace = Order.make_trace ~header:"[Trace 1] " current_pname current_elem in
let second_trace = Order.make_trace ~header:"[Trace 2] " endpoint_pname elem in
let ltr = first_trace @ second_trace in
let loc = Order.get_loc current_elem in
ReportMap.add_deadlock tenv current_pdesc loc ltr error_message report_map
| _, _ ->
report_map
in
let report_on_current_elem elem report_map =
match elem.Order.elem.eventually.elem with
| MayBlock _ ->
report_map
in
let report_on_current_elem elem report_map =
match elem.Order.elem.eventually.elem with
| MayBlock _ ->
report_map
| LockAcquire endpoint_lock ->
Lock.owner_class endpoint_lock
|> Option.value_map ~default:report_map ~f:(fun endpoint_class ->
(* get the class of the root variable of the lock in the endpoint elem
| LockAcquire endpoint_lock ->
Lock.owner_class endpoint_lock
|> Option.value_map ~default:report_map ~f:(fun endpoint_class ->
(* get the class of the root variable of the lock in the endpoint elem
and retrieve all the summaries of the methods of that class *)
(* for each summary related to the endpoint, analyse and report on its pairs *)
fold_reportable_summaries env endpoint_class ~init:report_map
~f:(fun acc (endp_pname, endpoint_summary) ->
let endp_order = endpoint_summary.order in
let endp_ui = endpoint_summary.ui in
if UIThreadDomain.is_empty ui || UIThreadDomain.is_empty endp_ui then
OrderDomain.fold (report_endpoint_elem elem endp_pname) endp_order acc
else acc ) )
in
OrderDomain.fold report_on_current_elem order report_map'
(* for each summary related to the endpoint, analyse and report on its pairs *)
fold_reportable_summaries env endpoint_class ~init:report_map
~f:(fun acc (endp_pname, endpoint_summary) ->
let endp_order = endpoint_summary.order in
let endp_ui = endpoint_summary.ui in
if UIThreadDomain.is_empty ui || UIThreadDomain.is_empty endp_ui then
OrderDomain.fold (report_endpoint_elem elem endp_pname) endp_order acc
else acc ) )
in
OrderDomain.fold report_on_current_elem order report_map'
let report_starvation env {StarvationDomain.events; ui} report_map' =
@ -439,11 +451,27 @@ let report_starvation env {StarvationDomain.events; ui} report_map' =
order acc
else acc ) )
in
match ui with
| AbstractDomain.Types.Bottom ->
report_map'
| AbstractDomain.Types.NonBottom ui_explain ->
EventDomain.fold (report_on_current_elem ui_explain) events report_map'
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
| AbstractDomain.Types.Bottom ->
report_map'
| AbstractDomain.Types.NonBottom ui_explain ->
EventDomain.fold (report_on_current_elem ui_explain) events report_map'
let reporting {Callbacks.procedures; source_file} =

@ -0,0 +1,13 @@
# 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

@ -0,0 +1,30 @@
/*
* 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();
}
}

@ -0,0 +1,30 @@
/*
* 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;
}
}

@ -0,0 +1,4 @@
codetoanalyze/java/strictmode/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/strictmode/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/strictmode/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/strictmode/java/GuardTest.java, java.GuardTest.testWriteBad():void, 13, STARVATION, no_bucket, ERROR, [`void GuardTest.testWriteBad()`,calls `void BlockGuard$Policy.onWriteToDisk()` from `void GuardTest.testWriteBad()`]

@ -0,0 +1,32 @@
/*
* 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();
}
}

@ -0,0 +1,129 @@
#!/bin/bash
# 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.
# USAGE
#
# 1. Follow instructions in https://source.android.com/setup/build/downloading to download
# the AOSP source and in https://source.android.com/setup/build/building to get proprietary
# binaries. You will need lots of space (at least 100Gb). Let <android-root> be its root as
# an *absolute path*.
#
# 2. Replace <android-root>/prebuilts/jdk/jdk9/<your OS>/bin/javac with the following script.
#
# #!/bin/bash
# infer -q --capture --continue --starvation-only --no-starvation \
# --project-root <android-root> --results-dir <android-root>/infer-out -- \
# /usr/local/bin/javac "$@"
#
# Here, my local installation of java is in /usr/local/, change accordingly. I used a
# Java *8* installation without problems, YMMV.
#
# 3. From <android-root> do
#
# $ . build/envsetup.sh
# $ export TEMPORARY_DISABLE_PATH_RESTRICTIONS=true
# $ cd libcore/ojluni
# $ mm -j1 javac-check
#
# ... and wait. It took me ~22h.
#
# 4. From <android-root> run
#
# $ infer analyze --starvation-only --dev-android-strict-mode
#
# 5. From <android-root> run this script, capturing stdout.
#
# $ <infer-root>/scripts/make-strict-mode.sh > \
# <infer-root>/infer/src/concurrency/StrictModeModels.ml
#
# 6. You may need to adapt the optional ~actuals_pred argument for methods in the above ML file.
# The aim is to avoid false positives when there is an overloaded method with different
# signatures and it so happens that one of the versions makes a violation when another does not.
# Recompile Infer.
SOURCE_FILES=$(grep "error:" infer-out/bugs.txt | cut -f1 -d: | sort -u | grep -v test )
MATCHERS=""
cat <<EOF
(*
* 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.
*)
open! IStd
open ConcurrencyModels
EOF
for SOURCE_FILE in $SOURCE_FILES ; do
PACKAGE=$(grep -E "^package " $SOURCE_FILE | cut -f2 -d' ' | cut -f1 -d\;)
if [[ $PACKAGE != android.* ]] && [[ $PACKAGE != androidx.* ]] && [[ $PACKAGE != java.* ]] ; then
continue
fi
BASENAME=$(basename $SOURCE_FILE )
CLASS=${BASENAME%.*}
if ! grep -q -E "public.*class.* ${CLASS}" $SOURCE_FILE ; then
continue
fi
HIDE=$(grep -B 2 -E "public.*class.* ${CLASS}" $SOURCE_FILE | grep '@hide')
if [ ! -z "$HIDE" ] ; then
continue
fi
FULLCLASSNAME="${PACKAGE}.${CLASS}"
METHOD_REXP="^ Method \`.* $CLASS\."
METHODS=$(grep -E "$METHOD_REXP" infer-out/bugs.txt | cut -f2 -d. | cut -f1 -d\` | sort -u)
if [ -z "$METHODS" ] ; then
continue
fi
HEADER=""
MATCHER="is_${CLASS}_method"
for METHOD in $METHODS; do
METHODNAME=$(echo $METHOD | cut -f1 -d\( )
if ! grep -q -E "public.*${METHODNAME}" $SOURCE_FILE ; then
continue
fi
if [ -z "$HEADER" ] ; then
echo "(* $SOURCE_FILE *)"
echo "let ${MATCHER} ="
echo " is_call_of_class \"${FULLCLASSNAME}\""
echo " ["
HEADER=true
fi
echo " \"${METHODNAME}\"; (* $METHOD *)"
done
if [ ! -z "$HEADER" ] ; then
echo " ]"
echo ' |> Staged.unstage'
echo
MATCHERS="$MATCHERS $MATCHER"
fi
done
echo
echo "let is_strict_mode_violation ="
echo " let matchers = ["
for M in $MATCHERS ; do
echo " $M ;"
done
echo " ]"
echo " in"
echo " fun tenv pn actuals -> List.exists matchers ~f:(fun m -> m tenv pn actuals)"
Loading…
Cancel
Save