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()));
}
}