diff options
| -rw-r--r-- | compiler/optimizing/fast_compiler_arm64.cc | 205 | ||||
| -rw-r--r-- | libartservice/service/java/com/android/server/art/DexUseManagerLocal.java | 76 | ||||
| -rw-r--r-- | libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java | 119 | ||||
| -rw-r--r-- | libartservice/service/proto/dex_use.proto | 14 | ||||
| -rw-r--r-- | libdexfile/dex/modifiers.h | 3 | ||||
| -rw-r--r-- | runtime/class_linker.cc | 3 | ||||
| -rw-r--r-- | runtime/entrypoints/entrypoint_utils-inl.h | 2 | ||||
| -rw-r--r-- | runtime/gc/collector/mark_compact.cc | 3 | ||||
| -rw-r--r-- | runtime/interpreter/mterp/nterp.cc | 10 | ||||
| -rw-r--r-- | runtime/mirror/class-inl.h | 10 | ||||
| -rw-r--r-- | runtime/mirror/class.h | 3 | ||||
| -rw-r--r-- | runtime/parsed_options.cc | 1 | ||||
| -rw-r--r-- | runtime/runtime.cc | 38 | ||||
| -rw-r--r-- | runtime/vdex_file.cc | 1 | ||||
| -rw-r--r-- | runtime/verifier/verifier_deps.cc | 1 | ||||
| -rwxr-xr-x | test/testrunner/testrunner.py | 2 | ||||
| -rw-r--r-- | tools/checker/Android.bp | 5 |
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", |