summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/optimizing/fast_compiler_arm64.cc205
-rw-r--r--libartservice/service/java/com/android/server/art/DexUseManagerLocal.java76
-rw-r--r--libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java119
-rw-r--r--libartservice/service/proto/dex_use.proto14
-rw-r--r--libdexfile/dex/modifiers.h3
-rw-r--r--runtime/class_linker.cc3
-rw-r--r--runtime/entrypoints/entrypoint_utils-inl.h2
-rw-r--r--runtime/gc/collector/mark_compact.cc3
-rw-r--r--runtime/interpreter/mterp/nterp.cc10
-rw-r--r--runtime/mirror/class-inl.h10
-rw-r--r--runtime/mirror/class.h3
-rw-r--r--runtime/parsed_options.cc1
-rw-r--r--runtime/runtime.cc38
-rw-r--r--runtime/vdex_file.cc1
-rw-r--r--runtime/verifier/verifier_deps.cc1
-rwxr-xr-xtest/testrunner/testrunner.py2
-rw-r--r--tools/checker/Android.bp5
17 files changed, 422 insertions, 74 deletions
diff --git a/compiler/optimizing/fast_compiler_arm64.cc b/compiler/optimizing/fast_compiler_arm64.cc
index 4ce4f45bee..f8cb2c7fe5 100644
--- a/compiler/optimizing/fast_compiler_arm64.cc
+++ b/compiler/optimizing/fast_compiler_arm64.cc
@@ -33,6 +33,7 @@
#pragma GCC diagnostic ignored "-Wshadow"
#include "aarch64/disasm-aarch64.h"
#include "aarch64/macro-assembler-aarch64.h"
+#include "aarch64/disasm-aarch64.h"
#pragma GCC diagnostic pop
using namespace vixl::aarch64; // NOLINT(build/namespaces)
@@ -119,6 +120,8 @@ class FastCompilerARM64 : public FastCompiler {
code_generation_data_(CodeGenerationData::Create(arena_stack, InstructionSet::kArm64)),
vreg_locations_(dex_compilation_unit.GetCodeItemAccessor().RegistersSize(),
allocator->Adapter()),
+ branch_targets_(dex_compilation_unit.GetCodeItemAccessor().InsnsSizeInCodeUnits(),
+ allocator->Adapter()),
has_frame_(false),
core_spill_mask_(0u),
fpu_spill_mask_(0u),
@@ -200,6 +203,10 @@ class FastCompilerARM64 : public FastCompiler {
// Return the existing register location for `reg`.
Location GetExistingRegisterLocation(uint32_t reg, DataType::Type type);
+ // Move dex registers holding constants into physical registers. Used when
+ // branching.
+ void MoveConstantsToRegisters();
+
// Generate code for one instruction.
bool ProcessDexInstruction(const Instruction& instruction,
uint32_t dex_pc,
@@ -214,6 +221,10 @@ class FastCompilerARM64 : public FastCompiler {
// Generate code for doing a Java invoke.
bool HandleInvoke(const Instruction& instruction, uint32_t dex_pc, InvokeType invoke_type);
+ // Generate code for IF_* instructions.
+ template<vixl::aarch64::Condition kCond, bool kCompareWithZero>
+ bool If_21_22t(const Instruction& instruction, uint32_t dex_pc);
+
// Generate code for doing a runtime invoke.
void InvokeRuntime(QuickEntrypointEnum entrypoint, uint32_t dex_pc);
@@ -257,6 +268,21 @@ class FastCompilerARM64 : public FastCompiler {
return !(is_non_null_mask_ & (1 << vreg_index));
}
+ // Get the label associated with the given `dex_pc`.
+ vixl::aarch64::Label* GetLabelOf(uint32_t dex_pc) {
+ return &branch_targets_[dex_pc];
+ }
+
+ // If we need to abort compilation, clear branch targets, required by vixl.
+ void AbortCompilation() {
+ for (vixl::aarch64::Label& label : branch_targets_) {
+ if (label.IsLinked()) {
+ __ Bind(&label);
+ }
+ }
+ }
+
+
// Compiler utilities.
//
Arm64Assembler* GetAssembler() { return &assembler_; }
@@ -302,6 +328,9 @@ class FastCompilerARM64 : public FastCompiler {
// The current location of each dex register.
ArenaVector<Location> vreg_locations_;
+ // A vector of size code units for dex pcs that are branch targets.
+ ArenaVector<vixl::aarch64::Label> branch_targets_;
+
// Whether we've created a frame for this compiled method.
bool has_frame_;
@@ -374,6 +403,17 @@ bool FastCompilerARM64::InitializeParameters() {
return true;
}
+void FastCompilerARM64::MoveConstantsToRegisters() {
+ for (uint32_t i = 0; i < vreg_locations_.size(); ++i) {
+ Location location = vreg_locations_[i];
+ if (location.IsConstant()) {
+ vreg_locations_[i] =
+ CreateNewRegisterLocation(i, DataType::Type::kInt32, /* next= */ nullptr);
+ MoveLocation(vreg_locations_[i], location, DataType::Type::kInt32);
+ }
+ }
+}
+
bool FastCompilerARM64::ProcessInstructions() {
DCHECK(GetCodeItemAccessor().HasCodeItem());
@@ -391,6 +431,11 @@ bool FastCompilerARM64::ProcessInstructions() {
const DexInstructionPcPair& next_pair = *it;
next = &next_pair.Inst();
}
+ vixl::aarch64::Label* label = GetLabelOf(pair.DexPc());
+ if (label->IsLinked()) {
+ MoveConstantsToRegisters();
+ __ Bind(label);
+ }
if (!ProcessDexInstruction(pair.Inst(), pair.DexPc(), next)) {
DCHECK(HitUnimplemented());
@@ -776,6 +821,11 @@ bool FastCompilerARM64::HandleInvoke(const Instruction& instruction,
} else if (invoke_type == kInterface) {
offset = resolved_method->GetImtIndex();
}
+
+ if (resolved_method->IsStringConstructor()) {
+ unimplemented_reason_ = "StringConstructor";
+ return false;
+ }
}
// Given we are calling a method, generate a frame.
@@ -1015,6 +1065,104 @@ bool FastCompilerARM64::CanGenerateCodeFor(ArtField* field, bool can_receiver_be
return true;
}
+#define DO_CASE(arm_op, op, other) \
+ case arm_op: { \
+ if (constant op other) { \
+ __ B(label); \
+ } \
+ return true; \
+ } \
+
+template<vixl::aarch64::Condition kCond, bool kCompareWithZero>
+bool FastCompilerARM64::If_21_22t(const Instruction& instruction, uint32_t dex_pc) {
+ DCHECK_EQ(kCompareWithZero ? Instruction::Format::k21t : Instruction::Format::k22t,
+ Instruction::FormatOf(instruction.Opcode()));
+ EnsureHasFrame();
+ int32_t target_offset = kCompareWithZero ? instruction.VRegB_21t() : instruction.VRegC_22t();
+ DCHECK_EQ(target_offset, instruction.GetTargetOffset());
+ if (target_offset < 0) {
+ // TODO: Support for negative branches requires two passes.
+ unimplemented_reason_ = "NegativeBranch";
+ return false;
+ }
+ int32_t register_index = kCompareWithZero ? instruction.VRegA_21t() : instruction.VRegA_22t();
+ vixl::aarch64::Label* label = GetLabelOf(dex_pc + target_offset);
+ Location location = vreg_locations_[register_index];
+
+ if (kCompareWithZero) {
+ // We are going to branch, move all constants to registers to make the merge
+ // point use the same locations.
+ MoveConstantsToRegisters();
+ if (location.IsConstant()) {
+ DCHECK(location.GetConstant()->IsIntConstant());
+ int32_t constant = location.GetConstant()->AsIntConstant()->GetValue();
+ switch (kCond) {
+ DO_CASE(vixl::aarch64::eq, ==, 0);
+ DO_CASE(vixl::aarch64::ne, !=, 0);
+ DO_CASE(vixl::aarch64::lt, <, 0);
+ DO_CASE(vixl::aarch64::le, <=, 0);
+ DO_CASE(vixl::aarch64::gt, >, 0);
+ DO_CASE(vixl::aarch64::ge, >=, 0);
+ }
+ return true;
+ } else if (location.IsRegister()) {
+ CPURegister reg = CPURegisterFrom(location, DataType::Type::kInt32);
+ switch (kCond) {
+ case vixl::aarch64::eq: {
+ __ Cbz(Register(reg), label);
+ return true;
+ }
+ case vixl::aarch64::ne: {
+ __ Cbnz(Register(reg), label);
+ return true;
+ }
+ default: {
+ __ Cmp(Register(reg), 0);
+ __ B(kCond, label);
+ return true;
+ }
+ }
+ } else {
+ DCHECK(location.IsStackSlot());
+ unimplemented_reason_ = "CompareWithZeroOnStackSlot";
+ }
+ return false;
+ }
+
+ // !kCompareWithZero
+ Location other_location = vreg_locations_[instruction.VRegB_22t()];
+ // We are going to branch, move all constants to registers to make the merge
+ // point use the same locations.
+ MoveConstantsToRegisters();
+ if (location.IsConstant() && other_location.IsConstant()) {
+ int32_t constant = location.GetConstant()->AsIntConstant()->GetValue();
+ int32_t other_constant = other_location.GetConstant()->AsIntConstant()->GetValue();
+ switch (kCond) {
+ DO_CASE(vixl::aarch64::eq, ==, other_constant);
+ DO_CASE(vixl::aarch64::ne, !=, other_constant);
+ DO_CASE(vixl::aarch64::lt, <, other_constant);
+ DO_CASE(vixl::aarch64::le, <=, other_constant);
+ DO_CASE(vixl::aarch64::gt, >, other_constant);
+ DO_CASE(vixl::aarch64::ge, >=, other_constant);
+ }
+ return true;
+ }
+ // Reload the locations, which can now be registers.
+ location = vreg_locations_[register_index];
+ other_location = vreg_locations_[instruction.VRegB_22t()];
+ if (location.IsRegister() && other_location.IsRegister()) {
+ CPURegister reg = CPURegisterFrom(location, DataType::Type::kInt32);
+ CPURegister other_reg = CPURegisterFrom(other_location, DataType::Type::kInt32);
+ __ Cmp(Register(reg), Register(other_reg));
+ __ B(kCond, label);
+ return true;
+ }
+
+ unimplemented_reason_ = "UnimplementedCompare";
+ return false;
+}
+#undef DO_CASE
+
bool FastCompilerARM64::ProcessDexInstruction(const Instruction& instruction,
uint32_t dex_pc,
const Instruction* next) {
@@ -1091,15 +1239,17 @@ bool FastCompilerARM64::ProcessDexInstruction(const Instruction& instruction,
}
#define IF_XX(comparison, cond) \
- case Instruction::IF_##cond: break; \
- case Instruction::IF_##cond##Z: break
-
- IF_XX(HEqual, EQ);
- IF_XX(HNotEqual, NE);
- IF_XX(HLessThan, LT);
- IF_XX(HLessThanOrEqual, LE);
- IF_XX(HGreaterThan, GT);
- IF_XX(HGreaterThanOrEqual, GE);
+ case Instruction::IF_##cond: \
+ return If_21_22t<comparison, /* kCompareWithZero= */ false>(instruction, dex_pc); \
+ case Instruction::IF_##cond##Z: \
+ return If_21_22t<comparison, /* kCompareWithZero= */ true>(instruction, dex_pc);
+
+ IF_XX(vixl::aarch64::eq, EQ);
+ IF_XX(vixl::aarch64::ne, NE);
+ IF_XX(vixl::aarch64::lt, LT);
+ IF_XX(vixl::aarch64::le, LE);
+ IF_XX(vixl::aarch64::gt, GT);
+ IF_XX(vixl::aarch64::ge, GE);
case Instruction::GOTO:
case Instruction::GOTO_16:
@@ -1114,6 +1264,15 @@ bool FastCompilerARM64::ProcessDexInstruction(const Instruction& instruction,
MoveLocation(convention.GetReturnLocation(return_type_),
vreg_locations_[register_index],
return_type_);
+ if (has_frame_) {
+ // We may have used the "record last instruction before return in return
+ // register" optimization (see `CreateNewRegisterLocation`),
+ // so set the returned register back to a callee save location in case the
+ // method has a frame and there are instructions after this return that
+ // may use this register.
+ uint32_t register_code = kAvailableCalleeSaveRegisters[register_index].GetCode();
+ vreg_locations_[register_index] = Location::RegisterLocation(register_code);
+ }
DropFrameAndReturn();
return true;
}
@@ -1960,13 +2119,16 @@ bool FastCompilerARM64::ProcessDexInstruction(const Instruction& instruction,
bool FastCompilerARM64::Compile() {
if (!InitializeParameters()) {
DCHECK(HitUnimplemented());
+ AbortCompilation();
return false;
}
if (!ProcessInstructions()) {
DCHECK(HitUnimplemented());
+ AbortCompilation();
return false;
}
if (HitUnimplemented()) {
+ AbortCompilation();
return false;
}
if (!has_frame_) {
@@ -1978,7 +2140,30 @@ bool FastCompilerARM64::Compile() {
/* is_debuggable= */ false);
}
code_generation_data_->GetStackMapStream()->EndMethod(assembler_.CodeSize());
- GetVIXLAssembler()->FinalizeCode();
+ assembler_.FinalizeCode();
+
+ if (VLOG_IS_ON(jit)) {
+ // Dump the generated code
+ {
+ ScopedObjectAccess soa(Thread::Current());
+ VLOG(jit) << "Dumping generated fast baseline code for " << method_->PrettyMethod();
+ }
+ FILE* file = tmpfile();
+ MacroAssembler* masm = GetVIXLAssembler();
+ PrintDisassembler print_disasm(file);
+ vixl::aarch64::Instruction* dis_start =
+ masm->GetBuffer()->GetStartAddress<vixl::aarch64::Instruction*>();
+ vixl::aarch64::Instruction* dis_end =
+ masm->GetBuffer()->GetEndAddress<vixl::aarch64::Instruction*>();
+ print_disasm.DisassembleBuffer(dis_start, dis_end);
+ fseek(file, 0L, SEEK_SET);
+ char buffer[1024];
+ const char* line;
+ while ((line = fgets(buffer, sizeof(buffer), file)) != nullptr) {
+ VLOG(jit) << std::string(line);
+ }
+ fclose(file);
+ }
return true;
}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
index 8d47cb6ba0..704dabe034 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -113,6 +113,16 @@ public class DexUseManagerLocal {
*/
@VisibleForTesting public static final long INTERVAL_MS = 15_000;
+ // Impose a limit on the input accepted by notifyDexContainersLoaded per owning package.
+ /** @hide */
+ @VisibleForTesting public static final int MAX_PATH_LENGTH = 4096;
+
+ /** @hide */
+ @VisibleForTesting public static final int MAX_CLASS_LOADER_CONTEXT_LENGTH = 10000;
+
+ /** @hide */
+ private static final int MAX_SECONDARY_DEX_FILES_PER_OWNER = 500;
+
private static final Object sLock = new Object();
// The static field is associated with the class and the class loader that loads it. In the
@@ -527,7 +537,7 @@ public class DexUseManagerLocal {
}
// Check remaining packages. Don't check for shared libraries because it might be too
- // expansive to do so and the time complexity is O(n) no matter we do it or not.
+ // expensive to do so and the time complexity is O(n) no matter we do it or not.
for (PackageState pkgState : packageStates.values()) {
if (visitedPackages.contains(pkgState.getPackageName())) {
continue;
@@ -657,16 +667,40 @@ public class DexUseManagerLocal {
private void addSecondaryDexUse(@NonNull String owningPackageName, @NonNull String dexPath,
@NonNull String loadingPackageName, boolean isolatedProcess,
@NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) {
+ DexLoader loader = DexLoader.create(loadingPackageName, isolatedProcess);
+ // This is to avoid a loading package from using up the SecondaryDexUse entries for another
+ // package (up to the MAX_SECONDARY_DEX_FILES_PER_OWNER limit). We don't care about the
+ // loading package messing up its own SecondaryDexUse entries.
+ // Note that we are using system_server's permission to check the existence. This is fine
+ // with the assumption that the file must be world readable to be used by other apps.
+ // We could use artd's permission to check the existence, and then there wouldn't be any
+ // permission issue, but that requires bringing up the artd service, which may be too
+ // expensive.
+ // TODO(jiakaiz): Check if the assumption is true.
+ if (isLoaderOtherApp(loader, owningPackageName) && !mInjector.pathExists(dexPath)) {
+ AsLog.w("Not recording non-existent secondary dex file '" + dexPath + "'");
+ return;
+ }
synchronized (mLock) {
+ PackageDexUse packageDexUse = mDexUse.mPackageDexUseByOwningPackageName.computeIfAbsent(
+ owningPackageName, k -> new PackageDexUse());
SecondaryDexUse secondaryDexUse =
- mDexUse.mPackageDexUseByOwningPackageName
- .computeIfAbsent(owningPackageName, k -> new PackageDexUse())
- .mSecondaryDexUseByDexFile.computeIfAbsent(
- dexPath, k -> new SecondaryDexUse());
+ packageDexUse.mSecondaryDexUseByDexFile.computeIfAbsent(dexPath, k -> {
+ if (packageDexUse.mSecondaryDexUseByDexFile.size()
+ >= mInjector.getMaxSecondaryDexFilesPerOwner()) {
+ AsLog.w("Not recording too many secondary dex use entries for "
+ + owningPackageName);
+ return null;
+ }
+ return new SecondaryDexUse();
+ });
+ if (secondaryDexUse == null) {
+ return;
+ }
secondaryDexUse.mUserHandle = mInjector.getCallingUserHandle();
- SecondaryDexUseRecord record = secondaryDexUse.mRecordByLoader.computeIfAbsent(
- DexLoader.create(loadingPackageName, isolatedProcess),
- k -> new SecondaryDexUseRecord());
+ SecondaryDexUseRecord record =
+ secondaryDexUse.mRecordByLoader.computeIfAbsent(
+ loader, k -> new SecondaryDexUseRecord());
record.mClassLoaderContext = classLoaderContext;
record.mAbiName = abiName;
record.mLastUsedAtMs = lastUsedAtMs;
@@ -772,13 +806,23 @@ public class DexUseManagerLocal {
}
for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
- Utils.assertNonEmpty(entry.getKey());
- String errorMsg = ArtJni.validateDexPath(entry.getKey());
+ String dexPath = entry.getKey();
+ String classLoaderContext = entry.getValue();
+ Utils.assertNonEmpty(dexPath);
+ if (dexPath.length() > MAX_PATH_LENGTH) {
+ throw new IllegalArgumentException(
+ "Dex path too long - exceeds " + MAX_PATH_LENGTH + " chars");
+ }
+ String errorMsg = ArtJni.validateDexPath(dexPath);
if (errorMsg != null) {
throw new IllegalArgumentException(errorMsg);
}
- Utils.assertNonEmpty(entry.getValue());
- errorMsg = ArtJni.validateClassLoaderContext(entry.getKey(), entry.getValue());
+ Utils.assertNonEmpty(classLoaderContext);
+ if (classLoaderContext.length() > MAX_CLASS_LOADER_CONTEXT_LENGTH) {
+ throw new IllegalArgumentException("Class loader context too long - exceeds "
+ + MAX_CLASS_LOADER_CONTEXT_LENGTH + " chars");
+ }
+ errorMsg = ArtJni.validateClassLoaderContext(dexPath, classLoaderContext);
if (errorMsg != null) {
throw new IllegalArgumentException(errorMsg);
}
@@ -1354,6 +1398,10 @@ public class DexUseManagerLocal {
return System.currentTimeMillis();
}
+ public boolean pathExists(String path) {
+ return new File(path).exists();
+ }
+
@NonNull
public String getFilename() {
return FILENAME;
@@ -1404,5 +1452,9 @@ public class DexUseManagerLocal {
public boolean isIsolatedUid(int uid) {
return Process.isIsolatedUid(uid);
}
+
+ public int getMaxSecondaryDexFilesPerOwner() {
+ return MAX_SECONDARY_DEX_FILES_PER_OWNER;
+ }
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
index d404b4cbda..f0002b80d7 100644
--- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -85,6 +86,9 @@ public class DexUseManagerTest {
private static final String INVISIBLE_BASE_APK =
"/somewhere/app/" + INVISIBLE_PKG_NAME + "/base.apk";
+ // A reduced limit to make the test run faster.
+ private static final int MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING = 50;
+
@Rule
public StaticMockitoRule mockitoRule = new StaticMockitoRule(
SystemProperties.class, Constants.class, ArtJni.class);
@@ -173,6 +177,7 @@ public class DexUseManagerTest {
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l);
+ lenient().when(mInjector.pathExists(any())).thenReturn(true);
lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath());
lenient()
.when(mInjector.createScheduledExecutor())
@@ -185,6 +190,9 @@ public class DexUseManagerTest {
lenient().when(mInjector.getCallingUserHandle()).thenReturn(mUserHandle);
lenient().when(mInjector.getCallingUid()).thenReturn(110001);
lenient().when(mInjector.isIsolatedUid(anyInt())).thenReturn(false);
+ lenient()
+ .when(mInjector.getMaxSecondaryDexFilesPerOwner())
+ .thenReturn(MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING);
mDexUseManager = new DexUseManagerLocal(mInjector);
mDexUseManager.systemReady();
@@ -639,11 +647,11 @@ public class DexUseManagerTest {
@Test
public void testCheckedSecondaryDexNotFound() throws Exception {
- when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
-
mDexUseManager.notifyDexContainersLoaded(
mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/foo.apk", "CLC"));
+ when(mArtd.getDexFileVisibility(mCeDir + "/foo.apk")).thenReturn(FileVisibility.NOT_FOUND);
+
assertThat(mDexUseManager.getCheckedSecondaryDexInfo(
OWNING_PKG_NAME, true /* excludeObsoleteDexesAndLoaders */))
.isEmpty();
@@ -825,6 +833,18 @@ public class DexUseManagerTest {
}
@Test(expected = IllegalArgumentException.class)
+ public void testTooLongDexPath() throws Exception {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+ Map.of("/" + "X".repeat(DexUseManagerLocal.MAX_PATH_LENGTH), "CLC"));
+ }
+
+ @Test
+ public void testMaxLengthDexPath() throws Exception {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+ Map.of("/" + "X".repeat(DexUseManagerLocal.MAX_PATH_LENGTH - 1), "CLC"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
public void testInvalidDexPath() throws Exception {
lenient().when(ArtJni.validateDexPath(any())).thenReturn("invalid");
mDexUseManager.notifyDexContainersLoaded(
@@ -832,6 +852,20 @@ public class DexUseManagerTest {
}
@Test(expected = IllegalArgumentException.class)
+ public void testTooLongClassLoaderContext() throws Exception {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+ Map.of(mCeDir + "/foo.apk",
+ "X".repeat(DexUseManagerLocal.MAX_CLASS_LOADER_CONTEXT_LENGTH + 1)));
+ }
+
+ @Test
+ public void testMaxLengthClassLoaderContext() throws Exception {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, OWNING_PKG_NAME,
+ Map.of(mCeDir + "/foo.apk",
+ "X".repeat(DexUseManagerLocal.MAX_CLASS_LOADER_CONTEXT_LENGTH)));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
public void testInvalidClassLoaderContext() throws Exception {
lenient().when(ArtJni.validateClassLoaderContext(any(), any())).thenReturn("invalid");
mDexUseManager.notifyDexContainersLoaded(
@@ -874,6 +908,87 @@ public class DexUseManagerTest {
mSnapshot, OWNING_PKG_NAME, Map.of(BASE_APK, "CLC"));
}
+ @Test
+ public void testExistingExternalSecondaryDexPath() throws Exception {
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ long oldFileSize = mTempFile.length();
+
+ String existingDexPath = mCeDir + "/foo.apk";
+ when(mInjector.pathExists(existingDexPath)).thenReturn(true);
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(existingDexPath, "PCL[]"));
+
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isGreaterThan(oldFileSize);
+ }
+
+ @Test
+ public void testNonexistingExternalSecondaryDexPath() throws Exception {
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ long oldFileSize = mTempFile.length();
+
+ String nonexistingDexPath = mCeDir + "/foo.apk";
+ when(mInjector.pathExists(nonexistingDexPath)).thenReturn(false);
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(nonexistingDexPath, "PCL[]"));
+
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isEqualTo(oldFileSize);
+ }
+
+ @Test
+ public void testInternalSecondaryDexPath() throws Exception {
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ long oldFileSize = mTempFile.length();
+
+ String nonexistingDexPath = mCeDir + "/foo.apk";
+ lenient().when(mInjector.pathExists(nonexistingDexPath)).thenReturn(false);
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(nonexistingDexPath, "PCL[]"));
+ verify(mArtd, never()).getDexFileVisibility(nonexistingDexPath);
+
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isGreaterThan(oldFileSize);
+ }
+
+ @Test
+ public void testLimitSecondaryDexFiles() throws Exception {
+ for (int n = 0; n < MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING - 1; ++n) {
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME,
+ Map.of(String.format("%s/%04d/foo.apk", mCeDir, n), "CLC"));
+ }
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ long oldFileSize = mTempFile.length();
+
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/9998/foo.apk", "CLC"));
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isGreaterThan(oldFileSize);
+
+ oldFileSize = mTempFile.length();
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, LOADING_PKG_NAME, Map.of(mCeDir + "/9999/foo.apk", "CLC"));
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isEqualTo(oldFileSize);
+
+ // Can still add loading packages to existing entries after the limit is reached.
+ mDexUseManager.notifyDexContainersLoaded(
+ mSnapshot, OWNING_PKG_NAME, Map.of(mCeDir + "/9998/foo.apk", "CLC"));
+ mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save.
+ assertThat(mTempFile.length()).isGreaterThan(oldFileSize);
+ }
+
+ @Test
+ public void testLimitSecondaryDexFilesSingleCall() throws Exception {
+ Map<String, String> clcByDexFile = new HashMap<>();
+ for (int n = 0; n < MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING + 1; ++n) {
+ clcByDexFile.put(String.format("%s/%04d/foo.apk", mCeDir, n), "CLC");
+ }
+ mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME, clcByDexFile);
+ assertThat(mDexUseManager.getSecondaryDexInfo(OWNING_PKG_NAME))
+ .hasSize(MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING);
+ }
+
private AndroidPackage createPackage(String packageName) {
AndroidPackage pkg = mock(AndroidPackage.class);
lenient().when(pkg.getStorageUuid()).thenReturn(StorageManager.UUID_DEFAULT);
diff --git a/libartservice/service/proto/dex_use.proto b/libartservice/service/proto/dex_use.proto
index 1dd962dbf4..1960882ad5 100644
--- a/libartservice/service/proto/dex_use.proto
+++ b/libartservice/service/proto/dex_use.proto
@@ -29,31 +29,31 @@ message DexUseProto {
}
message PackageDexUseProto {
- string owning_package_name = 1;
+ string owning_package_name = 1; // key
repeated PrimaryDexUseProto primary_dex_use = 2;
repeated SecondaryDexUseProto secondary_dex_use = 3;
}
message PrimaryDexUseProto {
- string dex_file = 1;
+ string dex_file = 1; // key
repeated PrimaryDexUseRecordProto record = 2;
}
message PrimaryDexUseRecordProto {
- string loading_package_name = 1;
- bool isolated_process = 2;
+ string loading_package_name = 1; // key
+ bool isolated_process = 2; // key
int64 last_used_at_ms = 3;
}
message SecondaryDexUseProto {
- string dex_file = 1;
+ string dex_file = 1; // key
Int32Value user_id = 2; // Must be explicitly set.
repeated SecondaryDexUseRecordProto record = 3;
}
message SecondaryDexUseRecordProto {
- string loading_package_name = 1;
- bool isolated_process = 2;
+ string loading_package_name = 1; // key
+ bool isolated_process = 2; // key
string class_loader_context = 3;
string abi_name = 4;
int64 last_used_at_ms = 5;
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 94e25e8e6e..ad6472df57 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -59,6 +59,9 @@ static constexpr uint32_t kAccObsoleteObject = 0x00200000; // class (run
// Set during boot image compilation to indicate that the class is
// not initialized at compile time and not in the list of preloaded classes.
static constexpr uint32_t kAccInBootImageAndNotInPreloadedClasses = 0x00400000; // class (runtime)
+// Set after verification if at least one of the class's method has unresolved
+// type checks failures.
+static constexpr uint32_t kAccHasTypeChecksFailure = 0x00800000; // class (runtime)
// This is set by the class linker during LinkInterfaceMethods. It is used by a method
// to represent that it was copied from its declaring class into another class.
// We need copies of the original method because the method may end up in different
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index b4720c2dbc..70eea2d268 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -5311,6 +5311,9 @@ verifier::FailureKind ClassLinker::VerifyClass(Thread* self,
Runtime::Current()->GetCompilerCallbacks()->UpdateClassState(
ClassReference(&klass->GetDexFile(), klass->GetDexClassDefIndex()), klass->GetStatus());
} else {
+ if (verifier_failure == verifier::FailureKind::kTypeChecksFailure) {
+ klass->SetHasTypeChecksFailure();
+ }
mirror::Class::SetStatus(klass, ClassStatus::kVerified, self);
}
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index f0f8f8dce3..77bf24f80b 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -416,7 +416,7 @@ inline ArtField* ResolveFieldWithAccessChecks(Thread* self,
return nullptr;
}
- if (resolve_field_type != 0u) {
+ if (resolve_field_type != 0u && caller->GetDeclaringClass()->HasTypeChecksFailure()) {
StackArtFieldHandleScope<1> rhs(self);
ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(resolved_field));
if (resolved_field->ResolveType().IsNull()) {
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index 1f2e911b8e..1f55013315 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -4343,8 +4343,9 @@ void MarkCompact::ScanDirtyObjects(bool paused, uint8_t minimum_age) {
break;
}
TimingLogger::ScopedTiming t(name, GetTimings());
- if (paused && young_gen_ &&
+ if (paused && use_generational_ &&
space->GetGcRetentionPolicy() == space::kGcRetentionPolicyAlwaysCollect) {
+ DCHECK_EQ(minimum_age, accounting::CardTable::kCardDirty);
auto mod_visitor = [](uint8_t* card, uint8_t cur_val) {
DCHECK_EQ(cur_val, accounting::CardTable::kCardDirty);
*card = accounting::CardTable::kCardAged;
diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc
index 8ce7e42a84..95cfe7fb7d 100644
--- a/runtime/interpreter/mterp/nterp.cc
+++ b/runtime/interpreter/mterp/nterp.cc
@@ -398,8 +398,8 @@ static ArtField* FindFieldFast(ArtMethod* caller, uint16_t field_index)
return nullptr;
}
- const dex::FieldId& field_id = caller->GetDexFile()->GetFieldId(field_index);
ObjPtr<mirror::Class> cls = caller->GetDeclaringClass();
+ const dex::FieldId& field_id = cls->GetDexFile().GetFieldId(field_index);
if (cls->GetDexTypeIndex() == field_id.class_idx_) {
// Field is in the same class as the caller, no need to do access checks.
return cls->FindDeclaredField(field_index);
@@ -464,7 +464,9 @@ extern "C" size_t NterpGetStaticField(Thread* self,
// fail to resolve the type, we clear the exception to keep interpreter
// semantics of not throwing when null is stored.
bool update_cache = true;
- if (opcode == Instruction::SPUT_OBJECT && resolved_field->ResolveType() == nullptr) {
+ if (opcode == Instruction::SPUT_OBJECT &&
+ caller->GetDeclaringClass()->HasTypeChecksFailure() &&
+ resolved_field->ResolveType() == nullptr) {
DCHECK(self->IsExceptionPending());
if (resolve_field_type) {
return 0;
@@ -513,7 +515,9 @@ extern "C" uint32_t NterpGetInstanceFieldOffset(Thread* self,
// fail to resolve the type, we clear the exception to keep interpreter
// semantics of not throwing when null is stored.
bool update_cache = true;
- if (opcode == Instruction::IPUT_OBJECT && resolved_field->ResolveType() == nullptr) {
+ if (opcode == Instruction::IPUT_OBJECT &&
+ caller->GetDeclaringClass()->HasTypeChecksFailure() &&
+ resolved_field->ResolveType() == nullptr) {
DCHECK(self->IsExceptionPending());
if (resolve_field_type != 0u) {
return 0;
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 373b9b3105..06b5cf5e9f 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -1336,6 +1336,16 @@ inline void Class::SetHasDefaultMethods() {
SetAccessFlagsDuringLinking(flags | kAccHasDefaultMethod);
}
+inline void Class::SetHasTypeChecksFailure() {
+ uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_));
+ SetAccessFlags(flags | kAccHasTypeChecksFailure);
+}
+
+inline bool Class::HasTypeChecksFailure() {
+ uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_));
+ return (flags & kAccHasTypeChecksFailure) != 0u;
+}
+
inline void Class::ClearFinalizable() {
// We're clearing the finalizable flag only for `Object` and `Enum`
// during early setup without the boot image.
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 6e72a41592..c8b234b789 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -288,6 +288,9 @@ class EXPORT MANAGED Class final : public Object {
ALWAYS_INLINE void SetHasDefaultMethods() REQUIRES_SHARED(Locks::mutator_lock_);
+ ALWAYS_INLINE void SetHasTypeChecksFailure() REQUIRES_SHARED(Locks::mutator_lock_);
+ ALWAYS_INLINE bool HasTypeChecksFailure() REQUIRES_SHARED(Locks::mutator_lock_);
+
ALWAYS_INLINE void SetFinalizable() REQUIRES_SHARED(Locks::mutator_lock_) {
uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_));
SetAccessFlagsDuringLinking(flags | kAccClassIsFinalizable);
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index d574f8e139..ab07ea0f02 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -427,7 +427,6 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize
.WithValueMap(hiddenapi_policy_valuemap)
.IntoKey(M::HiddenApiPolicy)
.Define("-Xcore-platform-api-policy:_")
- .WithHelp("Ignored for SDK level 36+.")
.WithType<hiddenapi::EnforcementPolicy>()
.WithValueMap(hiddenapi_policy_valuemap)
.IntoKey(M::CorePlatformApiPolicy)
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 6f2822fda0..23e06ab792 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1725,37 +1725,13 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
hidden_api_policy_ = runtime_options.GetOrDefault(Opt::HiddenApiPolicy);
DCHECK_IMPLIES(is_zygote_, hidden_api_policy_ == hiddenapi::EnforcementPolicy::kDisabled);
- // Set core platform API enforcement policy. Always enabled if the platform
- // SDK level is 36+, otherwise the checks are disabled by default and can be
- // enabled with a command line flag. AndroidRuntime will pass the flag if a
- // system property is set.
- {
- bool always_enable = false;
-#ifdef ART_TARGET_ANDROID
- int device_sdk_version = android_get_device_api_level();
- if (device_sdk_version >= 36) {
- always_enable = true;
- } else if (device_sdk_version == 35) {
- std::string codename =
- android::base::GetProperty("ro.build.version.codename", /*default_value=*/"");
- always_enable = (codename == "Baklava");
- }
-#endif
- const char* reason;
- if (always_enable) {
- core_platform_api_policy_ = hiddenapi::EnforcementPolicy::kEnabled;
- reason = "for Android 16+";
- } else {
- core_platform_api_policy_ = runtime_options.GetOrDefault(Opt::CorePlatformApiPolicy);
- reason = "by runtime option";
- }
- if (core_platform_api_policy_ != hiddenapi::EnforcementPolicy::kDisabled) {
- LOG(INFO) << "Core platform API "
- << (core_platform_api_policy_ == hiddenapi::EnforcementPolicy::kEnabled
- ? "enforcement"
- : "reporting")
- << " enabled " << reason;
- }
+ // Set core platform API enforcement policy. The checks are disabled by default and
+ // can be enabled with a command line flag. AndroidRuntime will pass the flag if
+ // a system property is set.
+ core_platform_api_policy_ = runtime_options.GetOrDefault(Opt::CorePlatformApiPolicy);
+ if (core_platform_api_policy_ != hiddenapi::EnforcementPolicy::kDisabled) {
+ LOG(INFO) << "Core platform API reporting enabled, enforcing="
+ << (core_platform_api_policy_ == hiddenapi::EnforcementPolicy::kEnabled ? "true" : "false");
}
// Dex2Oat's Runtime does not need the signal chain or the fault handler
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index c8f1bfd810..32a83f0e05 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -569,6 +569,7 @@ ClassStatus VdexFile::ComputeClassStatus(Thread* self, Handle<mirror::Class> cls
class_linker, self, source_desc, source_desc_length, class_loader));
if (destination == nullptr || source == nullptr) {
+ cls->SetHasTypeChecksFailure();
// The interpreter / compiler can handle a missing class.
continue;
}
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index ed7629f805..5cb36db679 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -746,6 +746,7 @@ bool VerifierDeps::VerifyDexFileAndUpdateStatus(
class_linker, self, source_desc, source_desc_length, class_loader));
if (destination == nullptr || source == nullptr) {
+ deps.verified_classes_[class_def_index] = false;
// We currently don't use assignability information for unresolved
// types, as the status of the class using unresolved types will be soft
// fail in the vdex.
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 5892345067..66412501f9 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -1211,7 +1211,7 @@ def main():
if build:
build_targets = []
# Build only the needed shards (depending on the selected tests).
- shards = set(re.search("(\d\d)-", t).group(1) for t in tests)
+ shards = set(re.search(r"(\d\d)-", t).group(1) for t in tests)
if any("hiddenapi" in t for t in tests):
shards.add("HiddenApi") # Include special HiddenApi shard.
for mode in ['host', 'target', 'jvm']:
diff --git a/tools/checker/Android.bp b/tools/checker/Android.bp
index db2c597bf4..8b14807854 100644
--- a/tools/checker/Android.bp
+++ b/tools/checker/Android.bp
@@ -29,11 +29,6 @@ python_binary_host {
"**/*.py",
],
main: "checker.py",
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
test_suites: [
"general-tests",
"mts-art",