Fix HClinitCheck elimination in instruction builder.
To handle escaping instances of erroneous classes correctly,
we can omit the HClinitCheck only when using a class in the
static method of the very same class. Even for superclasses
we need to do the check. The new test exposes the cases
where we were previously diverging from the RI.
Also clean up the CompilerDriver by inlining one function
directly to the HInstructionBuild::IsInitialized(.) and
removing some related functions that are no longer used.
The size of the aosp_taimen-userdebug prebuilts:
- before:
arm/boot*.oat: 16891788
arm64/boot*.oat: 19815520
oat/arm64/services.odex: 20071624
- after:
arm/boot*.oat: 16949532 (+56.4KiB, +0.34%)
arm64/boot*.oat: 19889752 (+72.5KiB, +0.37%)
oat/arm64/services.odex: 20224328 (+149.1KiB, +0.76%)
with minor changes to other app prebuilts.
Note: Some of that could be reclaimed by reinstating the old
optimization for classes where no bytecode can be executed
during initialization (no <clinit> to execute in that class
or superclasses).
Test: 174-escaping-instance-of-bad-class
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: testrunner.py --jvm -t 174
Test: Pixel 2 XL boots.
Test: testrunner.py --target --optimizing
Bug: 62478025
Change-Id: I41f026ea7fecc615c06e87f3b6cb847de0ede8a6
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 7e6fdaf..b24b036 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -112,7 +112,6 @@
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 @@
}
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 @@
#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 @@
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 @@
}
}
-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 0a8754a..3d3583c 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -197,18 +197,6 @@
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 @@
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 731accd..3cc20b3 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -1143,32 +1143,48 @@
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())) {
+ // 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;
+ }
+ }
+ }
+
+ // 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;
}
- if (IsSubClass(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;
}
@@ -1922,11 +1938,6 @@
}
}
-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 af7092a..578172a 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -98,11 +98,6 @@
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 @@
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 0000000..e287759
--- /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 0000000..55b4fed
--- /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 0000000..4346152
--- /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 f6561cd..2351c23 100644
--- a/test/450-checker-types/src/Main.java
+++ b/test/450-checker-types/src/Main.java
@@ -699,7 +699,7 @@
/// 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 752e761..ca92e7a 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 @@
}
/*
- * 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 @@
}
/*
- * 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 5ec30480..86fca80 100644
--- a/test/551-checker-clinit/src/Main.java
+++ b/test/551-checker-clinit/src/Main.java
@@ -33,15 +33,15 @@
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 eb985d0..3a5fe33 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -409,6 +409,7 @@
/// 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++;