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: c30bcedb3master
parent
3aab371b1f
commit
96e698a458
@ -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? *)
|
@ -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…
Reference in new issue