diff options
| -rw-r--r-- | compiler/driver/compiler_driver.cc | 86 | ||||
| -rw-r--r-- | compiler/driver/compiler_driver.h | 16 | ||||
| -rw-r--r-- | compiler/optimizing/instruction_builder.cc | 53 | ||||
| -rw-r--r-- | compiler/optimizing/instruction_builder.h | 8 | ||||
| -rw-r--r-- | test/174-escaping-instance-of-bad-class/expected.txt | 18 | ||||
| -rw-r--r-- | test/174-escaping-instance-of-bad-class/info.txt | 1 | ||||
| -rw-r--r-- | test/174-escaping-instance-of-bad-class/src/Main.java | 165 | ||||
| -rw-r--r-- | test/450-checker-types/src/Main.java | 2 | ||||
| -rw-r--r-- | test/478-checker-clinit-check-pruning/src/Main.java | 28 | ||||
| -rw-r--r-- | test/551-checker-clinit/src/Main.java | 8 | ||||
| -rw-r--r-- | test/706-checker-scheduler/src/Main.java | 1 |
11 files changed, 235 insertions, 151 deletions
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index 7e6fdaf633..b24b0362a3 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -112,7 +112,6 @@ class CompilerDriver::AOTCompilationStats { public: AOTCompilationStats() : stats_lock_("AOT compilation statistics lock"), - resolved_types_(0), unresolved_types_(0), resolved_instance_fields_(0), unresolved_instance_fields_(0), resolved_local_static_fields_(0), resolved_static_fields_(0), unresolved_static_fields_(0), type_based_devirtualization_(0), @@ -127,7 +126,6 @@ class CompilerDriver::AOTCompilationStats { } void Dump() { - DumpStat(resolved_types_, unresolved_types_, "types resolved"); DumpStat(resolved_instance_fields_, unresolved_instance_fields_, "instance fields resolved"); DumpStat(resolved_local_static_fields_ + resolved_static_fields_, unresolved_static_fields_, "static fields resolved"); @@ -177,16 +175,6 @@ class CompilerDriver::AOTCompilationStats { #define STATS_LOCK() #endif - void TypeDoesntNeedAccessCheck() REQUIRES(!stats_lock_) { - STATS_LOCK(); - resolved_types_++; - } - - void TypeNeedsAccessCheck() REQUIRES(!stats_lock_) { - STATS_LOCK(); - unresolved_types_++; - } - void ResolvedInstanceField() REQUIRES(!stats_lock_) { STATS_LOCK(); resolved_instance_fields_++; @@ -233,9 +221,6 @@ class CompilerDriver::AOTCompilationStats { private: Mutex stats_lock_; - size_t resolved_types_; - size_t unresolved_types_; - size_t resolved_instance_fields_; size_t unresolved_instance_fields_; @@ -1334,77 +1319,6 @@ void CompilerDriver::UpdateImageClasses(TimingLogger* timings) { } } -bool CompilerDriver::CanAssumeClassIsLoaded(mirror::Class* klass) { - Runtime* runtime = Runtime::Current(); - if (!runtime->IsAotCompiler()) { - DCHECK(runtime->UseJitCompilation()); - // Having the klass reference here implies that the klass is already loaded. - return true; - } - if (!GetCompilerOptions().IsBootImage()) { - // Assume loaded only if klass is in the boot image. App classes cannot be assumed - // loaded because we don't even know what class loader will be used to load them. - bool class_in_image = runtime->GetHeap()->FindSpaceFromObject(klass, false)->IsImageSpace(); - return class_in_image; - } - std::string temp; - const char* descriptor = klass->GetDescriptor(&temp); - return GetCompilerOptions().IsImageClass(descriptor); -} - -bool CompilerDriver::CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class, - ObjPtr<mirror::Class> resolved_class) { - if (resolved_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - return false; // Unknown class needs access checks. - } - bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. - if (!is_accessible) { - if (referrer_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - return false; // Incomplete referrer knowledge needs access check. - } - // Perform access check, will return true if access is ok or false if we're going to have to - // check this at runtime (for example for class loaders). - is_accessible = referrer_class->CanAccess(resolved_class); - } - if (is_accessible) { - stats_->TypeDoesntNeedAccessCheck(); - } else { - stats_->TypeNeedsAccessCheck(); - } - return is_accessible; -} - -bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class, - ObjPtr<mirror::Class> resolved_class, - bool* finalizable) { - if (resolved_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - // Be conservative. - *finalizable = true; - return false; // Unknown class needs access checks. - } - *finalizable = resolved_class->IsFinalizable(); - bool is_accessible = resolved_class->IsPublic(); // Public classes are always accessible. - if (!is_accessible) { - if (referrer_class == nullptr) { - stats_->TypeNeedsAccessCheck(); - return false; // Incomplete referrer knowledge needs access check. - } - // Perform access and instantiable checks, will return true if access is ok or false if we're - // going to have to check this at runtime (for example for class loaders). - is_accessible = referrer_class->CanAccess(resolved_class); - } - bool result = is_accessible && resolved_class->IsInstantiable(); - if (result) { - stats_->TypeDoesntNeedAccessCheck(); - } else { - stats_->TypeNeedsAccessCheck(); - } - return result; -} - void CompilerDriver::ProcessedInstanceField(bool resolved) { if (!resolved) { stats_->UnresolvedInstanceField(); diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 0a8754a6a6..3d3583cb9b 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -197,18 +197,6 @@ class CompilerDriver { uint16_t class_def_index) REQUIRES(!requires_constructor_barrier_lock_); - // Are runtime access checks necessary in the compiled code? - bool CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class, - ObjPtr<mirror::Class> resolved_class) - REQUIRES_SHARED(Locks::mutator_lock_); - - // Are runtime access and instantiable checks necessary in the code? - // out_is_finalizable is set to whether the type is finalizable. - bool CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class, - ObjPtr<mirror::Class> resolved_class, - bool* out_is_finalizable) - REQUIRES_SHARED(Locks::mutator_lock_); - // Resolve compiling method's class. Returns null on failure. ObjPtr<mirror::Class> ResolveCompilingMethodsClass(const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache, @@ -324,10 +312,6 @@ class CompilerDriver { return &compiled_method_storage_; } - // Can we assume that the klass is loaded? - bool CanAssumeClassIsLoaded(mirror::Class* klass) - REQUIRES_SHARED(Locks::mutator_lock_); - const ProfileCompilationInfo* GetProfileCompilationInfo() const { return profile_compilation_info_; } diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index ba160e55f8..771e066d2f 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -1147,32 +1147,48 @@ void HInstructionBuilder::BuildConstructorFenceForAllocation(HInstruction* alloc MethodCompilationStat::kConstructorFenceGeneratedNew); } -static bool IsSubClass(ObjPtr<mirror::Class> to_test, ObjPtr<mirror::Class> super_class) - REQUIRES_SHARED(Locks::mutator_lock_) { - return to_test != nullptr && !to_test->IsInterface() && to_test->IsSubClass(super_class); -} - bool HInstructionBuilder::IsInitialized(Handle<mirror::Class> cls) const { if (cls == nullptr) { return false; } - // `CanAssumeClassIsLoaded` will return true if we're JITting, or will - // check whether the class is in an image for the AOT compilation. - if (cls->IsInitialized() && - compiler_driver_->CanAssumeClassIsLoaded(cls.Get())) { - return true; + // Check if the class will be initialized at runtime. + if (cls->IsInitialized()) { + Runtime* runtime = Runtime::Current(); + if (!runtime->IsAotCompiler()) { + DCHECK(runtime->UseJitCompilation()); + // For JIT, the class cannot revert to an uninitialized state. + return true; + } + // Assume loaded only if klass is in the boot image. App classes cannot be assumed + // loaded because we don't even know what class loader will be used to load them. + const CompilerOptions& compiler_options = compiler_driver_->GetCompilerOptions(); + if (compiler_options.IsBootImage()) { + std::string temp; + const char* descriptor = cls->GetDescriptor(&temp); + if (compiler_options.IsImageClass(descriptor)) { + return true; + } + } else { + if (runtime->GetHeap()->FindSpaceFromObject(cls.Get(), false)->IsImageSpace()) { + return true; + } + } } - if (IsSubClass(GetOutermostCompilingClass(), cls.Get())) { + // We can avoid the class initialization check for `cls` only in static methods in the + // very same class. Instance methods of the same class can run on an escaped instance + // of an erroneous class. Even a superclass may need to be checked as the subclass + // can be completely initialized while the superclass is initializing and the subclass + // remains initialized when the superclass initializer throws afterwards. b/62478025 + // Note: The HClinitCheck+HInvokeStaticOrDirect merging can still apply. + if ((dex_compilation_unit_->GetAccessFlags() & kAccStatic) != 0u && + GetOutermostCompilingClass() == cls.Get()) { return true; } - // TODO: We should walk over the inlined methods, but we don't pass - // that information to the builder. - if (IsSubClass(GetCompilingClass(), cls.Get())) { - return true; - } + // Note: We could walk over the inlined methods to avoid allocating excessive + // `HClinitCheck`s in inlined static methods but they shall be eliminated by GVN. return false; } @@ -1926,11 +1942,6 @@ void HInstructionBuilder::BuildTypeCheck(const Instruction& instruction, } } -bool HInstructionBuilder::NeedsAccessCheck(dex::TypeIndex type_index, bool* finalizable) const { - return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks( - LookupReferrerClass(), LookupResolvedType(type_index, *dex_compilation_unit_), finalizable); -} - bool HInstructionBuilder::CanDecodeQuickenedInfo() const { return !quicken_info_.IsNull(); } diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h index af7092a0cf..578172a18e 100644 --- a/compiler/optimizing/instruction_builder.h +++ b/compiler/optimizing/instruction_builder.h @@ -98,11 +98,6 @@ class HInstructionBuilder : public ValueObject { void InitializeParameters(); - // Returns whether the current method needs access check for the type. - // Output parameter finalizable is set to whether the type is finalizable. - bool NeedsAccessCheck(dex::TypeIndex type_index, /*out*/bool* finalizable) const - REQUIRES_SHARED(Locks::mutator_lock_); - template<typename T> void Unop_12x(const Instruction& instruction, DataType::Type type, uint32_t dex_pc); @@ -287,8 +282,7 @@ class HInstructionBuilder : public ValueObject { void BuildConstructorFenceForAllocation(HInstruction* allocation); // Return whether the compiler can assume `cls` is initialized. - bool IsInitialized(Handle<mirror::Class> cls) const - REQUIRES_SHARED(Locks::mutator_lock_); + bool IsInitialized(Handle<mirror::Class> cls) const REQUIRES_SHARED(Locks::mutator_lock_); // Try to resolve a method using the class linker. Return null if a method could // not be resolved. diff --git a/test/174-escaping-instance-of-bad-class/expected.txt b/test/174-escaping-instance-of-bad-class/expected.txt new file mode 100644 index 0000000000..e287759d44 --- /dev/null +++ b/test/174-escaping-instance-of-bad-class/expected.txt @@ -0,0 +1,18 @@ +Bad.foo() +Bad.instanceValue = 33 +Caught NoClassDefFoundError. +BadSuper.foo() +BadSuper.superInstanceValue = 1 +Caught NoClassDefFoundError. +BadSub.bar() +BadSub.subInstanceValue = 11 +BadSub.subStaticValue = 4242 +BadSuper.superInstanceValue = 111 +Caught NoClassDefFoundError. +BadSub.bar() +BadSub.subInstanceValue = -1 +BadSub.subStaticValue = 4242 +BadSuper.superInstanceValue = -2 +Caught NoClassDefFoundError. +BadSub.allocSuper(.) +Caught NoClassDefFoundError. diff --git a/test/174-escaping-instance-of-bad-class/info.txt b/test/174-escaping-instance-of-bad-class/info.txt new file mode 100644 index 0000000000..55b4fed2cd --- /dev/null +++ b/test/174-escaping-instance-of-bad-class/info.txt @@ -0,0 +1 @@ +Regression test for an escaping instance of an erroneous class. diff --git a/test/174-escaping-instance-of-bad-class/src/Main.java b/test/174-escaping-instance-of-bad-class/src/Main.java new file mode 100644 index 0000000000..4346152f6c --- /dev/null +++ b/test/174-escaping-instance-of-bad-class/src/Main.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2018 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. + */ + +public class Main { + public static void main(String args[]) { + simpleTest(); + hierarchyTest(); + } + + public static void simpleTest() { + // Partial initialization of Bad; ignoring the error. + Error badClinit = null; + try { + new Bad(11); + } catch (Error e) { + badClinit = e; + } + // Call foo() on the escaped instance of Bad. + try { + bad.foo(); + } catch (NoClassDefFoundError ncdfe) { + // On RI, the NCDFE has no cause. On ART, the badClinit is the cause. + if (ncdfe.getCause() == badClinit || ncdfe.getCause() == null) { + System.out.println("Caught NoClassDefFoundError."); + } else { + ncdfe.printStackTrace(); + } + } + } + + public static void hierarchyTest() { + // Partial initialization of BadSuper; ignoring the error. Fully initializes BadSub. + Error badClinit = null; + try { + new BadSuper(0); + } catch (Error e) { + badClinit = e; + } + // Call BadSuper.foo() on the escaped instance of BadSuper. + try { + badSuper.foo(); + } catch (NoClassDefFoundError ncdfe) { + // On RI, the NCDFE has no cause. On ART, the badClinit is the cause. + if (ncdfe.getCause() == badClinit || ncdfe.getCause() == null) { + System.out.println("Caught NoClassDefFoundError."); + } else { + ncdfe.printStackTrace(); + } + } + + // Call BadSub.bar() on the escaped instance of BadSub. + try { + badSub.bar(); + } catch (NoClassDefFoundError ncdfe) { + // On RI, the NCDFE has no cause. On ART, the badClinit is the cause. + if (ncdfe.getCause() == badClinit || ncdfe.getCause() == null) { + System.out.println("Caught NoClassDefFoundError."); + } else { + ncdfe.printStackTrace(); + } + } + + // Test that we can even create instances of BadSub with erroneous superclass BadSuper. + try { + new BadSub(-1, -2).bar(); + } catch (NoClassDefFoundError ncdfe) { + // On RI, the NCDFE has no cause. On ART, the badClinit is the cause. + if (ncdfe.getCause() == badClinit || ncdfe.getCause() == null) { + System.out.println("Caught NoClassDefFoundError."); + } else { + ncdfe.printStackTrace(); + } + } + + // Test that we cannot create instances of BadSuper from BadSub. + try { + badSub.allocSuper(11111); // Should throw. + System.out.println("Allocated BadSuper!"); + } catch (NoClassDefFoundError ncdfe) { + // On RI, the NCDFE has no cause. On ART, the badClinit is the cause. + if (ncdfe.getCause() == badClinit || ncdfe.getCause() == null) { + System.out.println("Caught NoClassDefFoundError."); + } else { + ncdfe.printStackTrace(); + } + } + } + + public static Bad bad; + + public static BadSuper badSuper; + public static BadSub badSub; +} + +class Bad { + static { + // Create an instance of Bad and let it escape in Main.bad. + Main.bad = new Bad(33); + staticValue = 42; + if (true) { throw new Error("Bad <clinit>"); } + } + public void foo() { + System.out.println("Bad.foo()"); + System.out.println("Bad.instanceValue = " + instanceValue); + System.out.println("Bad.staticValue = " + staticValue); + } + public Bad(int iv) { instanceValue = iv; } + public int instanceValue; + public static int staticValue; +} + +class BadSuper { + static { + Main.badSuper = new BadSuper(1); + Main.badSub = new BadSub(11, 111); // Fully initializes BadSub. + BadSuper.superStaticValue = 42; + BadSub.subStaticValue = 4242; + if (true) { throw new Error("Bad <clinit>"); } + } + public void foo() { + System.out.println("BadSuper.foo()"); + System.out.println("BadSuper.superInstanceValue = " + superInstanceValue); + System.out.println("BadSuper.superStaticValue = " + superStaticValue); + } + public BadSuper(int superiv) { superInstanceValue = superiv; } + public int superInstanceValue; + public static int superStaticValue; +} + +// Note: If we tried to initialize BadSub before BadSuper, it would end up erroneous +// because the superclass fails initialization. However, since we start initializing the +// BadSuper first, BadSub is initialized successfully while BadSuper is "initializing" +// and remains initialized after the BadSuper's class initializer throws. +class BadSub extends BadSuper { + public void bar() { + System.out.println("BadSub.bar()"); + System.out.println("BadSub.subInstanceValue = " + subInstanceValue); + System.out.println("BadSub.subStaticValue = " + subStaticValue); + System.out.println("BadSuper.superInstanceValue = " + superInstanceValue); + System.out.println("BadSuper.superStaticValue = " + superStaticValue); + } + public BadSuper allocSuper(int superiv) { + System.out.println("BadSub.allocSuper(.)"); + return new BadSuper(superiv); + } + public BadSub(int subiv, int superiv) { + super(superiv); + subInstanceValue = subiv; + } + public int subInstanceValue; + public static int subStaticValue; +} diff --git a/test/450-checker-types/src/Main.java b/test/450-checker-types/src/Main.java index f6561cd046..2351c2333b 100644 --- a/test/450-checker-types/src/Main.java +++ b/test/450-checker-types/src/Main.java @@ -699,7 +699,7 @@ public class Main { /// CHECK-DAG: <<NCArg:l\d+>> NullCheck [<<Arg>>] klass:SubclassA /// CHECK-DAG: InvokeVirtual [<<NCArg>>] method_name:java.lang.Object.hashCode - public void testExplicitArgumentMoreSpecific(SubclassA obj) { + public static void testExplicitArgumentMoreSpecific(SubclassA obj) { // Inlining a method will build it with reference types from its signature, // here the callee graph is built with Super as the type of its only argument. // Running RTP after its ParameterValue instructions are replaced with actual diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java index 752e761f1f..ca92e7a86a 100644 --- a/test/478-checker-clinit-check-pruning/src/Main.java +++ b/test/478-checker-clinit-check-pruning/src/Main.java @@ -185,21 +185,20 @@ public class Main { } /* - * Ensure an inlined call to a static method whose declaring class - * is a super class of the caller's class does not require an - * explicit clinit check. + * We used to remove clinit check for calls to static methods in a superclass. However, this + * is not a valid optimization when instances of erroneous classes can escape. b/62478025 */ /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after) /// CHECK-DAG: InvokeStaticOrDirect /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() builder (after) - /// CHECK-NOT: LoadClass - /// CHECK-NOT: ClinitCheck + /// CHECK: LoadClass + /// CHECK: ClinitCheck /// CHECK-START: void Main$SubClassOfClassWithClinit5.invokeStaticInlined() inliner (after) - /// CHECK-NOT: LoadClass - /// CHECK-NOT: ClinitCheck + /// CHECK: LoadClass + /// CHECK: ClinitCheck /// CHECK-NOT: InvokeStaticOrDirect static class ClassWithClinit5 { @@ -218,25 +217,22 @@ public class Main { } /* - * Ensure an non-inlined call to a static method whose declaring - * class is a super class of the caller's class does not require an - * explicit clinit check. + * We used to remove clinit check for calls to static methods in a superclass. However, this + * is not a valid optimization when instances of erroneous classes can escape. b/62478025 */ /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after) /// CHECK-DAG: InvokeStaticOrDirect /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() builder (after) - /// CHECK-NOT: LoadClass - /// CHECK-NOT: ClinitCheck + /// CHECK: LoadClass + /// CHECK: ClinitCheck /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after) + /// CHECK-DAG: LoadClass + /// CHECK-DAG: ClinitCheck /// CHECK-DAG: InvokeStaticOrDirect - /// CHECK-START: void Main$SubClassOfClassWithClinit6.invokeStaticNotInlined() inliner (after) - /// CHECK-NOT: LoadClass - /// CHECK-NOT: ClinitCheck - static class ClassWithClinit6 { static boolean doThrow = false; diff --git a/test/551-checker-clinit/src/Main.java b/test/551-checker-clinit/src/Main.java index 5ec304808b..86fca80292 100644 --- a/test/551-checker-clinit/src/Main.java +++ b/test/551-checker-clinit/src/Main.java @@ -33,15 +33,15 @@ public class Main { class Sub extends Main { /// CHECK-START: void Sub.invokeSuperClass() builder (after) - /// CHECK-NOT: ClinitCheck + /// CHECK: ClinitCheck public void invokeSuperClass() { - int a = Main.foo; + int a = Main.foo; // Class initialization check must be preserved. b/62478025 } /// CHECK-START: void Sub.invokeItself() builder (after) - /// CHECK-NOT: ClinitCheck + /// CHECK: ClinitCheck public void invokeItself() { - int a = foo; + int a = foo; // Class initialization check must be preserved. b/62478025 } /// CHECK-START: void Sub.invokeSubClass() builder (after) diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java index eb985d0032..3a5fe335ab 100644 --- a/test/706-checker-scheduler/src/Main.java +++ b/test/706-checker-scheduler/src/Main.java @@ -409,6 +409,7 @@ public class Main { /// CHECK-DAG: StaticFieldSet /// CHECK-DAG: StaticFieldSet public void accessFields() { + static_variable = 0; // Force ClinitCheck outside the loop. b/62478025 my_obj = new ExampleObj(1, 2); for (int i = 0; i < 10; i++) { my_obj.n1++; |