summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/driver/compiler_driver.cc86
-rw-r--r--compiler/driver/compiler_driver.h16
-rw-r--r--compiler/optimizing/instruction_builder.cc53
-rw-r--r--compiler/optimizing/instruction_builder.h8
-rw-r--r--test/174-escaping-instance-of-bad-class/expected.txt18
-rw-r--r--test/174-escaping-instance-of-bad-class/info.txt1
-rw-r--r--test/174-escaping-instance-of-bad-class/src/Main.java165
-rw-r--r--test/450-checker-types/src/Main.java2
-rw-r--r--test/478-checker-clinit-check-pruning/src/Main.java28
-rw-r--r--test/551-checker-clinit/src/Main.java8
-rw-r--r--test/706-checker-scheduler/src/Main.java1
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++;