Add runtime unmodifiable check for PackageState

Introduces test that enforces that all members and their type
parameter children types that return collections/maps return non-null,
non-empty, unmodifiable objects when a test instance is constructed
and filled with data.

Makes the PackageImpl collections unmodifiable, but ignores the inner
objects with a constants list in the test. To be removed once the
inner object infrastructure is finished and can be marked immutable.

Bug: 235462722

Test: atest PackageManagerServiceUnitTests

Change-Id: I9759b7dd5c945e65498e22fcf408d53931dc502f
This commit is contained in:
Winson Chiu
2022-08-23 15:04:56 +00:00
parent 94d707ccef
commit fad1ef9961
10 changed files with 419 additions and 36 deletions

View File

@@ -329,17 +329,15 @@ class ApexPackageInfo {
ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
if (throwable == null) {
// Calling hideAsFinal to assign derived fields for the app info flags.
parseResult.parsedPackage.hideAsFinal();
// TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire
// calling path code
ScanPackageUtils.applyPolicy(parseResult.parsedPackage,
PackageManagerService.SCAN_AS_SYSTEM,
mPackageManager == null ? null : mPackageManager.getPlatformPackage(),
false);
results.add(new ApexManager.ScanResult(
ai, parseResult.parsedPackage, parseResult.parsedPackage.getPackageName()));
// Calling hideAsFinal to assign derived fields for the app info flags.
AndroidPackage finalPkg = parseResult.parsedPackage.hideAsFinal();
results.add(new ApexManager.ScanResult(ai, finalPkg, finalPkg.getPackageName()));
} else if (throwable instanceof PackageManagerException) {
throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
} else {

View File

@@ -3451,8 +3451,8 @@ final class InstallPackageHelper {
if (throwable == null) {
try {
AndroidPackage pkg = addForInitLI(
parseResult.parsedPackage, newParseFlags, newScanFlags, null);
addForInitLI(parseResult.parsedPackage, newParseFlags, newScanFlags, null);
AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal();
if (ai.isFactory && !ai.isActive) {
disableSystemPackageLPw(pkg);
}

View File

@@ -1207,12 +1207,24 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return pkgState.getUsesLibraryInfos();
}
@NonNull
public PackageSetting addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
pkgState.addUsesLibraryInfo(value);
return this;
}
@NonNull
@Override
public List<String> getUsesLibraryFiles() {
return pkgState.getUsesLibraryFiles();
}
@NonNull
public PackageSetting addUsesLibraryFile(String value) {
pkgState.addUsesLibraryFile(value);
return this;
}
@Override
public boolean isHiddenUntilInstalled() {
return pkgState.isHiddenUntilInstalled();

View File

@@ -385,16 +385,16 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@Nullable
@DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class)
private Boolean requestRawExternalStorageAccess;
// TODO(chiuwinson): Non-null
@Nullable
private ArraySet<String> mimeGroups;
@NonNull
@DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
private Set<String> mimeGroups = emptySet();
// Usually there's code to set enabled to true during parsing, but it's possible to install
// an APK targeting <R that doesn't contain an <application> tag. That code would be skipped
// and never assign this, so initialize this to true for those cases.
private long mBooleans = Booleans.ENABLED;
private long mBooleans2;
@Nullable
private Set<String> mKnownActivityEmbeddingCerts;
@NonNull
private Set<String> mKnownActivityEmbeddingCerts = emptySet();
// Derived fields
private long mLongVersionCode;
private int mLocaleConfigRes;
@@ -549,7 +549,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
if (mimeGroups != null && mimeGroups.size() > 500) {
throw new IllegalStateException("Max limit on number of MIME Groups reached");
}
mimeGroups = ArrayUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
mimeGroups = CollectionUtils.add(mimeGroups, filter.getMimeGroup(groupIndex));
}
}
}
@@ -935,8 +935,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@NonNull
@Override
public Set<String> getKnownActivityEmbeddingCerts() {
return mKnownActivityEmbeddingCerts == null ? Collections.emptySet()
: mKnownActivityEmbeddingCerts;
return mKnownActivityEmbeddingCerts;
}
@Override
@@ -1949,8 +1948,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
}
@Override
public ParsingPackage setKnownActivityEmbeddingCerts(
@Nullable Set<String> knownEmbeddingCerts) {
public ParsingPackage setKnownActivityEmbeddingCerts(@NonNull Set<String> knownEmbeddingCerts) {
mKnownActivityEmbeddingCerts = knownEmbeddingCerts;
return this;
}
@@ -2516,7 +2514,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
appInfo.setVersionCode(mLongVersionCode);
appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
appInfo.setLocaleConfigRes(mLocaleConfigRes);
if (mKnownActivityEmbeddingCerts != null) {
if (!mKnownActivityEmbeddingCerts.isEmpty()) {
appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
}
@@ -2593,11 +2591,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@Override
public AndroidPackageInternal hideAsFinal() {
// TODO(b/135203078): Lock as immutable
if (mStorageUuid == null) {
assignDerivedFields();
}
assignDerivedFields2();
makeImmutable();
return this;
}
@@ -2613,6 +2611,48 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
baseAppDataDir + Environment.DIR_USER_DE + systemUserSuffix);
}
private void makeImmutable() {
usesLibraries = Collections.unmodifiableList(usesLibraries);
usesOptionalLibraries = Collections.unmodifiableList(usesOptionalLibraries);
usesNativeLibraries = Collections.unmodifiableList(usesNativeLibraries);
usesOptionalNativeLibraries = Collections.unmodifiableList(usesOptionalNativeLibraries);
originalPackages = Collections.unmodifiableList(originalPackages);
adoptPermissions = Collections.unmodifiableList(adoptPermissions);
requestedPermissions = Collections.unmodifiableList(requestedPermissions);
protectedBroadcasts = Collections.unmodifiableList(protectedBroadcasts);
apexSystemServices = Collections.unmodifiableList(apexSystemServices);
activities = Collections.unmodifiableList(activities);
receivers = Collections.unmodifiableList(receivers);
services = Collections.unmodifiableList(services);
providers = Collections.unmodifiableList(providers);
permissions = Collections.unmodifiableList(permissions);
permissionGroups = Collections.unmodifiableList(permissionGroups);
instrumentations = Collections.unmodifiableList(instrumentations);
overlayables = Collections.unmodifiableMap(overlayables);
libraryNames = Collections.unmodifiableList(libraryNames);
usesStaticLibraries = Collections.unmodifiableList(usesStaticLibraries);
usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
configPreferences = Collections.unmodifiableList(configPreferences);
reqFeatures = Collections.unmodifiableList(reqFeatures);
featureGroups = Collections.unmodifiableList(featureGroups);
usesPermissions = Collections.unmodifiableList(usesPermissions);
usesSdkLibraries = Collections.unmodifiableList(usesSdkLibraries);
implicitPermissions = Collections.unmodifiableList(implicitPermissions);
upgradeKeySets = Collections.unmodifiableSet(upgradeKeySets);
keySetMapping = Collections.unmodifiableMap(keySetMapping);
attributions = Collections.unmodifiableList(attributions);
preferredActivityFilters = Collections.unmodifiableList(preferredActivityFilters);
processes = Collections.unmodifiableMap(processes);
mProperties = Collections.unmodifiableMap(mProperties);
queriesIntents = Collections.unmodifiableList(queriesIntents);
queriesPackages = Collections.unmodifiableList(queriesPackages);
queriesProviders = Collections.unmodifiableSet(queriesProviders);
mimeGroups = Collections.unmodifiableSet(mimeGroups);
mKnownActivityEmbeddingCerts = Collections.unmodifiableSet(mKnownActivityEmbeddingCerts);
}
@Override
public long getLongVersionCode() {
return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode);
@@ -3041,7 +3081,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
dest.writeIntArray(this.splitRevisionCodes);
sForBoolean.parcel(this.resizeableActivity, dest, flags);
dest.writeInt(this.autoRevokePermissions);
dest.writeArraySet(this.mimeGroups);
sForInternedStringSet.parcel(this.mimeGroups, dest, flags);
dest.writeInt(this.gwpAsanMode);
dest.writeSparseIntArray(this.minExtensionVersions);
dest.writeMap(this.mProperties);
@@ -3201,7 +3241,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
this.resizeableActivity = sForBoolean.unparcel(in);
this.autoRevokePermissions = in.readInt();
this.mimeGroups = (ArraySet<String>) in.readArraySet(boot);
this.mimeGroups = sForInternedStringSet.unparcel(in);
this.gwpAsanMode = in.readInt();
this.minExtensionVersions = in.readSparseIntArray();
this.mProperties = in.readHashMap(boot);
@@ -3224,6 +3264,9 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
assignDerivedFields();
assignDerivedFields2();
// Do not call makeImmutable here as cached parsing will need
// to mutate this instance before it's finalized.
}
@NonNull

View File

@@ -33,6 +33,7 @@ import com.android.server.pm.PackageSetting;
import com.android.server.pm.Settings;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -170,7 +171,7 @@ public class PackageStateImpl implements PackageState {
mLastModifiedTime = pkgState.getLastModifiedTime();
mLastUpdateTime = pkgState.getLastUpdateTime();
mLongVersionCode = pkgState.getVersionCode();
mMimeGroups = pkgState.getMimeGroups();
mMimeGroups = Collections.unmodifiableMap(pkgState.getMimeGroups());
mPath = pkgState.getPath();
mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
@@ -180,8 +181,8 @@ public class PackageStateImpl implements PackageState {
mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
mUsesLibraryInfos = pkgState.getUsesLibraryInfos();
mUsesLibraryFiles = pkgState.getUsesLibraryFiles();
mUsesLibraryInfos = Collections.unmodifiableList(pkgState.getUsesLibraryInfos());
mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
setBoolean(Booleans.INSTALL_PERMISSIONS_FIXED, pkgState.isInstallPermissionsFixed());
@@ -195,8 +196,8 @@ public class PackageStateImpl implements PackageState {
int userStatesSize = userStates.size();
mUserStates = new SparseArray<>(userStatesSize);
for (int index = 0; index < userStatesSize; index++) {
mUserStates.put(mUserStates.keyAt(index),
UserStateImpl.copy(mUserStates.valueAt(index)));
mUserStates.put(userStates.keyAt(index),
UserStateImpl.copy(userStates.valueAt(index)));
}
}

View File

@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
import com.android.server.pm.PackageSetting;
@@ -69,6 +70,18 @@ public class PackageStateUnserialized {
mPackageSetting = packageSetting;
}
@NonNull
public PackageStateUnserialized addUsesLibraryInfo(@NonNull SharedLibraryInfo value) {
usesLibraryInfos = CollectionUtils.add(usesLibraryInfos, value);
return this;
}
@NonNull
public PackageStateUnserialized addUsesLibraryFile(@NonNull String value) {
usesLibraryFiles = CollectionUtils.add(usesLibraryFiles, value);
return this;
}
private long[] lazyInitLastPackageUsageTimeInMills() {
return new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT];
}

View File

@@ -35,6 +35,7 @@ android_test {
"kotlin-reflect",
"services.core",
"servicestests-utils",
"servicestests-core-utils",
"truth-prebuilt",
],
platform_apis: true,

View File

@@ -23,7 +23,6 @@ import android.content.pm.FeatureGroupInfo
import android.content.pm.FeatureInfo
import android.content.pm.PackageManager
import android.content.pm.SigningDetails
import com.android.server.pm.pkg.parsing.ParsingPackage
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
@@ -65,6 +64,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
"addMimeGroupsFromComponent",
"assignDerivedFields",
"assignDerivedFields2",
"makeImmutable",
"buildFakeForDeletion",
"buildAppClassNamesByProcess",
"capPermissionPriorities",
@@ -219,6 +219,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::isNativeLibraryRootRequiresIsa,
AndroidPackage::isOdm,
AndroidPackage::isOem,
AndroidPackage::isOnBackInvokedCallbackEnabled,
AndroidPackage::isOverlay,
AndroidPackage::isOverlayIsStatic,
AndroidPackage::isPartiallyDirectBootAware,
@@ -279,7 +280,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
getSetByValue(
AndroidPackage::areAttributionsUserVisible,
ParsingPackage::setAttributionsAreUserVisible,
PackageImpl::setAttributionsAreUserVisible,
true
),
getSetByValue2(
@@ -513,11 +514,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
}
),
getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")),
getSetByValue(
AndroidPackage::isOnBackInvokedCallbackEnabled,
ParsingPackage::setOnBackInvokedCallbackEnabled,
true
)
)
override fun initialObject() = PackageImpl.forParsing(
@@ -560,10 +556,13 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
.setSplitHasCode(1, false)
.setSplitClassLoaderName(0, "testSplitClassLoaderNameZero")
.setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
.addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"))
.addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
override fun finalizeObject(parcelable: Parcelable) {
(parcelable as PackageImpl).hideAsParsed().hideAsFinal()
}
override fun extraAssertions(before: Parcelable, after: Parcelable) {
super.extraAssertions(before, after)
after as PackageImpl

View File

@@ -293,12 +293,21 @@ abstract class ParcelableComponentTest(
first?.let { property(it) } == second?.let { property(it) }
}
@Test
fun valueComparison() {
fun buildBefore(): Pair<List<Param>, Parcelable> {
val params = baseParams.mapNotNull(::buildParams) + extraParams().filterNotNull()
val before = initialObject()
params.forEach { it.setFunction(arrayOf(before, it.value())) }
finalizeObject(before)
return params to before
}
protected open fun finalizeObject(parcelable: Parcelable) {
}
@Test
fun valueComparison() {
val (params, before) = buildBefore()
val parcel = Parcel.obtain()
writeToParcel(parcel, before)
@@ -307,6 +316,15 @@ abstract class ParcelableComponentTest(
parcel.setDataPosition(0)
val baseline = initialObject()
finalizeObject(baseline)
val baselineParcel = Parcel.obtain()
writeToParcel(baselineParcel, baseline)
// Check that something substantial actually changed in the test object
expect.that(parcel.dataSize()).isGreaterThan(baselineParcel.dataSize())
val after = creator.createFromParcel(parcel)
expect.withMessage("Mismatched write and read data sizes")
@@ -314,6 +332,7 @@ abstract class ParcelableComponentTest(
.isEqualTo(dataSize)
parcel.recycle()
baselineParcel.recycle()
runAssertions(params, before, after)
}

View File

@@ -0,0 +1,297 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.pkg
import android.content.Intent
import android.content.pm.overlay.OverlayPaths
import android.content.pm.PackageManager
import android.content.pm.PathPermission
import android.content.pm.SharedLibraryInfo
import android.content.pm.VersionedPackage
import android.os.PatternMatcher
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.PackageSettingBuilder
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageState
import com.android.server.pm.pkg.PackageStateImpl
import com.android.server.pm.pkg.PackageUserState
import com.android.server.pm.pkg.PackageUserStateImpl
import com.android.server.pm.pkg.component.ParsedActivity
import com.android.server.pm.pkg.component.ParsedActivityImpl
import com.android.server.pm.pkg.component.ParsedComponentImpl
import com.android.server.pm.pkg.component.ParsedInstrumentation
import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
import com.android.server.pm.pkg.component.ParsedPermission
import com.android.server.pm.pkg.component.ParsedPermissionGroup
import com.android.server.pm.pkg.component.ParsedPermissionImpl
import com.android.server.pm.pkg.component.ParsedProcess
import com.android.server.pm.pkg.component.ParsedProcessImpl
import com.android.server.pm.pkg.component.ParsedProvider
import com.android.server.pm.pkg.component.ParsedProviderImpl
import com.android.server.pm.pkg.component.ParsedService
import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest
import com.google.common.truth.Expect
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.contracts.ExperimentalContracts
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.starProjectedType
class PackageStateTest {
companion object {
private val IGNORED_TYPES = listOf(
"java.io.File",
"java.lang.Boolean",
"java.lang.Byte",
"java.lang.CharSequence",
"java.lang.Character",
"java.lang.Double",
"java.lang.Float",
"java.lang.Integer",
"java.lang.Long",
"java.lang.Short",
"java.lang.String",
"java.lang.Void",
)
// STOPSHIP: Remove these and fix the implementations
private val IGNORED_FUNCTIONS = listOf(
ParsedActivity::getIntents,
ParsedActivity::getKnownActivityEmbeddingCerts,
ParsedActivity::getProperties,
ParsedInstrumentation::getIntents,
ParsedInstrumentation::getIntents,
ParsedInstrumentation::getProperties,
ParsedInstrumentation::getProperties,
ParsedPermission::getIntents,
ParsedPermission::getProperties,
ParsedPermissionGroup::getIntents,
ParsedPermissionGroup::getProperties,
ParsedProcess::getAppClassNamesByPackage,
ParsedProvider::getIntents,
ParsedProvider::getPathPermissions,
ParsedProvider::getProperties,
ParsedProvider::getUriPermissionPatterns,
ParsedService::getIntents,
ParsedService::getProperties,
SharedLibraryInfo::getAllCodePaths,
SharedLibraryInfo::getDependencies,
Intent::getCategories,
PackageUserState::getDisabledComponents,
PackageUserState::getEnabledComponents,
PackageUserState::getSharedLibraryOverlayPaths,
OverlayPaths::getOverlayPaths,
OverlayPaths::getResourceDirs,
)
}
@get:Rule
val tempFolder = TemporaryFolder()
@get:Rule
val expect = Expect.create()
private val collectionType = MutableCollection::class.starProjectedType
private val mapType = Map::class.starProjectedType
@OptIn(ExperimentalContracts::class)
@Test
fun collectionImmutability() {
val seenTypes = mutableSetOf<KType>()
val (_, pkg) = AndroidPackageTest().buildBefore()
val packageState = PackageSettingBuilder()
.setPackage(pkg as AndroidPackage)
.setCodePath(tempFolder.newFile().path)
.build()
fillMissingData(packageState, pkg as PackageImpl)
visitType(seenTypes, emptyList(), PackageStateImpl.copy(packageState),
PackageState::class.starProjectedType)
visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType)
visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0),
PackageUserState::class.starProjectedType)
// Don't check empties for defaults since their collections will always be empty
visitType(seenTypes, emptyList(), PackageUserState.DEFAULT,
PackageUserState::class.starProjectedType, enforceNonEmpty = false)
// Check that some minimum number of functions were validated,
// in case the type checking breaks somehow
expect.that(seenTypes.size).isGreaterThan(10)
}
/**
* Fill fields in [PackageState] and its children that are not filled by [AndroidPackageTest].
* Real objects and real invocations of the live APIs are necessary to ensure that the test
* mirrors real device behavior.
*/
private fun fillMissingData(pkgSetting: PackageSetting, pkg: PackageImpl) {
pkgSetting.addUsesLibraryFile("usesLibraryFile")
pkgSetting.addUsesLibraryInfo(SharedLibraryInfo(
"path",
"packageName",
listOf(tempFolder.newFile().path),
"name",
1,
0,
VersionedPackage("versionedPackage0", 1),
listOf(VersionedPackage("versionedPackage1", 2)),
emptyList(),
false
))
pkgSetting.addMimeTypes("mimeGroup", setOf("mimeType"))
pkgSetting.getOrCreateUserState(0).apply {
setEnabledComponents(ArraySet<String>().apply { add("com.test.EnabledComponent") })
setDisabledComponents(ArraySet<String>().apply { add("com.test.DisabledComponent") })
setSharedLibraryOverlayPaths("sharedLibrary",
OverlayPaths.Builder().addApkPath("/test/overlay.apk").build())
}
val property = PackageManager.Property("propertyName", 1, "com.test", null)
listOf(
pkg.activities,
pkg.receivers,
pkg.providers,
pkg.services,
pkg.instrumentations,
pkg.permissions,
pkg.permissionGroups
).map { it.first() as ParsedComponentImpl }
.forEach {
it.addIntent(ParsedIntentInfoImpl())
it.addProperty(property)
}
(pkg.activities.first() as ParsedActivityImpl).knownActivityEmbeddingCerts =
setOf("TESTEMBEDDINGCERT")
(pkg.permissions.first() as ParsedPermissionImpl).knownCerts = setOf("TESTEMBEDDINGCERT")
(pkg.providers.first() as ParsedProviderImpl).apply {
addPathPermission(PathPermission("pattern", PatternMatcher.PATTERN_LITERAL,
"readPermission", "writerPermission"))
addUriPermissionPattern(PatternMatcher("*", PatternMatcher.PATTERN_LITERAL))
}
(pkg.processes.values.first() as ParsedProcessImpl).apply {
deniedPermissions = setOf("deniedPermission")
putAppClassNameForPackage("package", "className")
}
}
private fun visitType(
seenTypes: MutableSet<KType>,
parentChain: List<String>,
impl: Any,
type: KType,
enforceNonEmpty: Boolean = true
) {
if (!seenTypes.add(type)) return
val kClass = type.classifier as KClass<*>
val qualifiedName = kClass.qualifiedName!!
if (IGNORED_TYPES.contains(qualifiedName)) return
val newChain = parentChain + kClass.simpleName!!
val newChainText = newChain.joinToString()
val filteredFunctions = kClass.memberFunctions
.filter {
// Size 1 because the impl receiver counts as a parameter
it.parameters.size == 1
}
.filterNot(IGNORED_FUNCTIONS::contains)
filteredFunctions.filter { it.returnType.isSubtypeOf(collectionType) }
.forEach {
val collection = it.call(impl)
if (collection as? MutableCollection<*> == null) {
expect.withMessage("Method $newChainText ${it.name} cannot return null")
.fail()
return@forEach
}
val value = try {
collection.stream().findFirst().get()!!
} catch (e: Exception) {
if (enforceNonEmpty) {
expect.withMessage("Method $newChainText ${it.name} returns empty")
.that(e)
.isNull()
return@forEach
} else null
}
if (value != null) {
it.returnType.arguments.forEach {
visitType(seenTypes, newChain, value, it.type!!)
}
}
// Must test clear last in case it works and actually clears the collection
expectUnsupported(newChain, it) { collection.clear() }
}
filteredFunctions.filter { it.returnType.isSubtypeOf(mapType) }
.forEach {
val map = it.call(impl)
if (map as? MutableMap<*, *> == null) {
expect.withMessage("Method $newChainText ${it.name} cannot return null")
.fail()
return@forEach
}
val entry = try {
map.entries.stream().findFirst().get()!!
} catch (e: Exception) {
expect.withMessage("Method $newChainText ${it.name} returns empty")
.that(e)
.isNull()
return@forEach
}
visitType(seenTypes, newChain, entry.key!!, it.returnType.arguments[0].type!!)
visitType(seenTypes, newChain, entry.value!!, it.returnType.arguments[1].type!!)
// Must test clear last in case it works and actually clears the map
expectUnsupported(newChain, it) { map.clear() }
}
}
private fun expectUnsupported(
parentChain: List<String>,
function: KFunction<*>,
block: () -> Unit
) {
val exception = try {
block()
null
} catch (e: UnsupportedOperationException) {
e
}
expect.withMessage("Method ${parentChain.joinToString()} $function doesn't throw")
.that(exception)
.isNotNull()
}
}