Extend field value propagation to handle clinits with side effects

Bug: 150349055
Change-Id: Id1da4aa99f7111a679ae95a7c439b39d94cbfb0c
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 8588a9d..7d2d844 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -350,6 +350,12 @@
         continue;
       }
       for (DexEncodedField field : clazz.fields()) {
+        // The field $r8$clinitField may be synthesized by R8 in order to trigger the initialization
+        // of the enclosing class. It is not present in the input, and therefore we do not require
+        // that it can be mapped back to the original program.
+        if (field.field.match(dexItemFactory.objectMembers.clinitField)) {
+          continue;
+        }
         DexField originalField = getOriginalFieldSignature(field.field);
         assert originalFields.contains(originalField)
             : "Unable to map field `" + field.field.toSourceString() + "` back to original program";
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 31ead7e..178e9b2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -6,13 +6,17 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.UseRegistry;
@@ -25,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
@@ -80,16 +85,38 @@
   }
 
   private void computeFieldsOfInterest(AppInfoWithLiveness appInfo) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (DexProgramClass clazz : appInfo.classes()) {
-      Iterable<DexEncodedField> fields =
-          clazz.classInitializationMayHaveSideEffects(appView)
-              ? clazz.instanceFields()
-              : clazz.fields();
-      for (DexEncodedField field : fields) {
-        if (canOptimizeField(field, appView) && appInfo.mayPropagateValueFor(field.field)) {
+      for (DexEncodedField field : clazz.instanceFields()) {
+        if (canOptimizeField(field, appView)) {
           fieldsOfInterest.add(field);
         }
       }
+      OptionalBool mayRequireClinitField = OptionalBool.unknown();
+      for (DexEncodedField field : clazz.staticFields()) {
+        if (canOptimizeField(field, appView)) {
+          if (mayRequireClinitField.isUnknown()) {
+            mayRequireClinitField =
+                OptionalBool.of(clazz.classInitializationMayHaveSideEffects(appView));
+          }
+          fieldsOfInterest.add(field);
+        }
+      }
+      if (mayRequireClinitField.isTrue()) {
+        DexField clinitField = dexItemFactory.objectMembers.clinitField;
+        if (clazz.lookupStaticField(dexItemFactory.objectMembers.clinitField) == null) {
+          FieldAccessFlags accessFlags =
+              FieldAccessFlags.fromSharedAccessFlags(
+                  Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_STATIC);
+          clazz.appendStaticField(
+              new DexEncodedField(
+                  dexItemFactory.createField(clazz.type, clinitField.type, clinitField.name),
+                  accessFlags,
+                  DexAnnotationSet.empty(),
+                  null));
+          appView.appInfo().invalidateTypeCacheFor(clazz.type);
+        }
+      }
     }
     assert verifyNoConstantFieldsOnSynthesizedClasses(appView);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index c934275..af990b1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -40,6 +40,8 @@
   private DominatorTree dominatorTree;
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
+  final Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
+
   FieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView,
       IRCode code,
@@ -78,7 +80,6 @@
     // Find all the static-put instructions that assign a field in the enclosing class which is
     // guaranteed to be assigned only in the current initializer.
     boolean isStraightLineCode = true;
-    Map<DexEncodedField, LinkedList<FieldInstruction>> putsPerField = new IdentityHashMap<>();
     for (BasicBlock block : code.blocks) {
       if (block.getSuccessors().size() >= 2) {
         isStraightLineCode = false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index f25c566..10dd287 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -55,6 +56,36 @@
   }
 
   @Override
+  void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
+    super.computeFieldOptimizationInfo(classInitializerDefaultsResult);
+
+    classInitializerDefaultsResult.forEachOptimizedField(
+        (field, value) -> {
+          if (putsPerField.containsKey(field)
+              || !appView.appInfo().isFieldOnlyWrittenInMethod(field, method)) {
+            return;
+          }
+
+          AbstractValueFactory factory = appView.abstractValueFactory();
+          if (value.isDexValueNumber()) {
+            feedback.recordFieldHasAbstractValue(
+                field,
+                appView,
+                factory.createSingleNumberValue(value.asDexValueNumber().getRawValue()));
+          } else if (value.isDexValueString()) {
+            feedback.recordFieldHasAbstractValue(
+                field,
+                appView,
+                factory.createSingleStringValue(value.asDexValueString().getValue()));
+          } else if (value.isDexItemBasedValueString()) {
+            // TODO(b/150835624): Extend to dex item based const strings.
+          } else {
+            assert false : value.getClass().getName();
+          }
+        });
+  }
+
+  @Override
   boolean isSubjectToOptimization(DexEncodedField field) {
     return field.isStatic()
         && field.holder() == clazz.type
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index d00ff3c..42e8e4a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -29,8 +29,13 @@
 
   public enum Assumption {
     NONE,
+    CLASS_ALREADY_INITIALIZED,
     RECEIVER_NOT_NULL;
 
+    boolean canAssumeClassIsAlreadyInitialized() {
+      return this == CLASS_ALREADY_INITIALIZED;
+    }
+
     boolean canAssumeReceiverIsNotNull() {
       return this == RECEIVER_NOT_NULL;
     }
@@ -132,7 +137,8 @@
     if (!appView.enableWholeProgramOptimizations()) {
       return AbstractError.bottom();
     }
-    boolean mayTriggerClassInitialization = isStaticGet() || isStaticPut();
+    boolean mayTriggerClassInitialization =
+        isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
     if (mayTriggerClassInitialization) {
       // Only check for <clinit> side effects if there is no -assumenosideeffects rule.
       if (appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 8deda3f..9c50aab 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -990,6 +990,10 @@
     return null;
   }
 
+  public boolean isStaticFieldInstruction() {
+    return false;
+  }
+
   public boolean isStaticGet() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
new file mode 100644
index 0000000..9c5a123
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticFieldInstruction.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.FieldInstruction.Assumption;
+
+public interface StaticFieldInstruction {
+
+  boolean hasOutValue();
+
+  Value outValue();
+
+  boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context, Assumption assumption);
+
+  FieldInstruction asFieldInstruction();
+
+  boolean isStaticFieldInstruction();
+
+  boolean isStaticGet();
+
+  StaticGet asStaticGet();
+
+  boolean isStaticPut();
+
+  StaticPut asStaticPut();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 08285c5..e7c2dee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -30,7 +30,7 @@
 import com.google.common.collect.Sets;
 import java.util.Set;
 
-public class StaticGet extends FieldInstruction {
+public class StaticGet extends FieldInstruction implements StaticFieldInstruction {
 
   public StaticGet(Value dest, DexField field) {
     super(field, dest, (Value) null);
@@ -137,7 +137,13 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
-    return instructionInstanceCanThrow(appView, context).isThrowing();
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
+    return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
   }
 
   @Override
@@ -181,6 +187,11 @@
   }
 
   @Override
+  public boolean isStaticFieldInstruction() {
+    return true;
+  }
+
+  @Override
   public boolean isStaticGet() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index ff1dd42..721a7bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.google.common.collect.Sets;
 
-public class StaticPut extends FieldInstruction {
+public class StaticPut extends FieldInstruction implements StaticFieldInstruction {
 
   public StaticPut(Value source, DexField field) {
     super(field, null, source);
@@ -95,6 +95,12 @@
 
   @Override
   public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    return instructionMayHaveSideEffects(appView, context, Assumption.NONE);
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(
+      AppView<?> appView, DexType context, Assumption assumption) {
     if (appView.appInfo().hasLiveness()) {
       AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
       // MemberValuePropagation will replace the field read only if the target field has bound
@@ -107,7 +113,7 @@
         return false;
       }
 
-      if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+      if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
         return true;
       }
 
@@ -195,6 +201,11 @@
   }
 
   @Override
+  public boolean isStaticFieldInstruction() {
+    return true;
+  }
+
+  @Override
   public boolean isStaticPut() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 16619d2..783ed8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -28,6 +31,9 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticFieldInstruction;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -197,12 +203,19 @@
       iterator.replaceCurrentInstruction(replacement);
     } else {
       assert lookup.type == RuleType.ASSUME_VALUES;
-      if (current.outValue() != null) {
+      BasicBlock block = current.getBlock();
+      Position position = current.getPosition();
+      if (current.hasOutValue()) {
         assert replacement.outValue() != null;
         current.outValue().replaceUsers(replacement.outValue());
       }
-      replacement.setPosition(current.getPosition());
-      if (current.getBlock().hasCatchHandlers()) {
+      if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        replaceStaticFieldInstructionByClinitAccessIfPossible(
+            staticGet, staticGet.getField().holder, code, iterator, code.method.holder());
+      }
+      replacement.setPosition(position);
+      if (block.hasCatchHandlers()) {
         iterator.split(code, blocks).listIterator(code).add(replacement);
       } else {
         iterator.add(replacement);
@@ -355,6 +368,9 @@
         if (current.isInstanceGet()) {
           replaceInstanceFieldInstructionByNullCheckIfPossible(
               current.asInstanceGet(), iterator, context);
+        } else {
+          replaceStaticFieldInstructionByClinitAccessIfPossible(
+              current.asStaticGet(), target.holder(), code, iterator, context);
         }
 
         // Insert the definition of the replacement.
@@ -401,13 +417,50 @@
       return;
     }
 
-    if (target.isStatic() != current.isStaticGet()) {
+    if (target.isStatic()) {
       return;
     }
 
     replaceInstanceFieldInstructionByNullCheckIfPossible(current, iterator, code.method.holder());
   }
 
+  private void replaceStaticPutByClinitAccessIfNeverRead(
+      IRCode code, InstructionListIterator iterator, StaticPut current) {
+    DexEncodedField field = appView.appInfo().resolveField(current.getField());
+    if (field == null || appView.appInfo().isFieldRead(field)) {
+      return;
+    }
+
+    if (!field.isStatic()) {
+      return;
+    }
+
+    replaceStaticFieldInstructionByClinitAccessIfPossible(
+        current, field.holder(), code, iterator, code.method.holder());
+  }
+
+  private void replaceStaticFieldInstructionByClinitAccessIfPossible(
+      StaticFieldInstruction instruction,
+      DexType holder,
+      IRCode code,
+      InstructionListIterator iterator,
+      DexType context) {
+    if (instruction.instructionMayHaveSideEffects(
+        appView, context, FieldInstruction.Assumption.CLASS_ALREADY_INITIALIZED)) {
+      return;
+    }
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
+    if (clazz != null) {
+      DexEncodedField clinitField =
+          clazz.lookupStaticField(appView.dexItemFactory().objectMembers.clinitField);
+      if (clinitField != null) {
+        Value dest = code.createValue(TypeLatticeElement.getInt());
+        StaticGet replacement = new StaticGet(dest, clinitField.field);
+        iterator.replaceCurrentInstruction(replacement);
+      }
+    }
+  }
+
   /**
    * Replace invoke targets and field accesses with constant values where possible.
    *
@@ -434,6 +487,8 @@
               code, affectedValues, blocks, iterator, current.asFieldInstruction());
         } else if (current.isInstancePut()) {
           replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
+        } else if (current.isStaticPut()) {
+          replaceStaticPutByClinitAccessIfNeverRead(code, iterator, current.asStaticPut());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9213a28..71e7a85 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -990,7 +990,7 @@
         if (target != null
             && target.method != dexItemFactory.enumMethods.finalize
             && target.method != dexItemFactory.objectMembers.finalize) {
-            return true;
+          return true;
         }
         return false;
       } else {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
index d133adf..c4d321a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/PutObjectWithFinalizeTest.java
@@ -32,7 +32,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public PutObjectWithFinalizeTest(TestParameters parameters) {
@@ -48,7 +48,7 @@
         .addOptionsModification(options -> options.enableClassStaticizer = false)
         .enableInliningAnnotations()
         .enableMergeAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
             inspector -> {
@@ -70,7 +70,7 @@
                       "otherArrayInstanceWithoutFinalizer");
               for (String name : presentFields) {
                 FieldSubject fieldSubject = classSubject.uniqueFieldWithName(name);
-                assertThat(fieldSubject, isPresent());
+                assertThat(name, fieldSubject, isPresent());
                 assertTrue(
                     mainSubject
                         .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
index 9c32e92..adc1f0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -56,7 +56,10 @@
     assertThat(aClassSubject, isPresent());
 
     FieldSubject fieldSubject = aClassSubject.uniqueFieldWithName("field");
-    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isPresent()));
+
+    FieldSubject clinitFieldSubject = aClassSubject.uniqueFieldWithName("$r8$clinit");
+    assertThat(clinitFieldSubject, isPresent());
 
     // B.method() is present.
     ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -81,7 +84,7 @@
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
-            .anyMatch(x -> x.getField() == fieldSubject.getField().field));
+            .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field));
     assertTrue(
         mainMethodSubject
             .streamInstructions()
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
index 94e96f1..0c6c8b8 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForConstantValuedFieldTest.java
@@ -19,7 +19,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public AssumeValuesForConstantValuedFieldTest(TestParameters parameters) {
@@ -32,7 +32,7 @@
         .addInnerClasses(AssumeValuesForConstantValuedFieldTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-assumevalues class * { static boolean field return false; }")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index 4f31790..9b8d209 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -50,13 +54,10 @@
   }
 
   private static void shaking8ThingClassIsAbstractAndEmpty(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz("shaking8.Thing");
-    Assert.assertTrue(clazz.isAbstract());
-    clazz.forAllMethods((method) -> Assert.fail());
-    clazz = inspector.clazz("shaking8.YetAnotherThing");
-    if (clazz.isPresent()) {
-      Assert.assertTrue(clazz.isAbstract());
-      clazz.forAllMethods((method) -> Assert.fail());
-    }
+    ClassSubject thingClass = inspector.clazz("shaking8.Thing");
+    Assert.assertTrue(thingClass.isAbstract());
+    thingClass.forAllMethods((method) -> Assert.fail());
+    ClassSubject yetAnotherThingClass = inspector.clazz("shaking8.YetAnotherThing");
+    assertThat(yetAnotherThingClass, not(isPresent()));
   }
 }