Propagate dynamic type information for static fields
Change-Id: Ie387919337c6356a8130d6415f10f2edc9f24d39
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index ef96f6a..c086a74 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -5,10 +5,13 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
public interface FieldOptimizationFeedback {
void markFieldCannotBeKept(DexEncodedField field);
void markFieldAsPropagated(DexEncodedField field);
+
+ void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d278966..0cbef69 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1203,7 +1203,7 @@
codeRewriter.rewriteThrowNullPointerException(code);
if (classInitializerDefaultsOptimization != null && !isDebugMode) {
- classInitializerDefaultsOptimization.optimize(method, code);
+ classInitializerDefaultsOptimization.optimize(method, code, feedback);
}
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 1f7a504..e305778 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -29,6 +29,8 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -43,6 +45,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -101,7 +104,7 @@
this.dexItemFactory = appView.dexItemFactory();
}
- public void optimize(DexEncodedMethod method, IRCode code) {
+ public void optimize(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
if (!method.isClassInitializer()) {
return;
}
@@ -119,7 +122,8 @@
findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts(code, clazz, unnecessaryStaticPuts);
// Return eagerly if there is nothing to optimize.
- if (unnecessaryStaticPuts.isEmpty()) {
+ if (finalFieldPuts.isEmpty()) {
+ assert unnecessaryStaticPuts.isEmpty();
return;
}
@@ -129,73 +133,85 @@
for (StaticPut put : finalFieldPuts) {
DexEncodedField field = appView.appInfo().resolveField(put.getField());
DexType fieldType = field.field.type;
- Value inValue = put.value();
- if (fieldType == dexItemFactory.stringType) {
- fieldsWithStaticValues.put(field, getDexStringValue(inValue, method.method.holder));
- } else if (fieldType.isClassType() || fieldType.isArrayType()) {
- if (inValue.isZero()) {
- fieldsWithStaticValues.put(field, DexValueNull.NULL);
+ Value value = put.value();
+ if (unnecessaryStaticPuts.contains(put)) {
+ if (fieldType == dexItemFactory.stringType) {
+ fieldsWithStaticValues.put(field, getDexStringValue(value, method.method.holder));
+ } else if (fieldType.isClassType() || fieldType.isArrayType()) {
+ if (value.isZero()) {
+ fieldsWithStaticValues.put(field, DexValueNull.NULL);
+ } else {
+ throw new Unreachable("Unexpected default value for field type " + fieldType + ".");
+ }
} else {
- throw new Unreachable("Unexpected default value for field type " + fieldType + ".");
+ ConstNumber cnst = value.getConstInstruction().asConstNumber();
+ if (fieldType == dexItemFactory.booleanType) {
+ fieldsWithStaticValues.put(field, DexValueBoolean.create(cnst.getBooleanValue()));
+ } else if (fieldType == dexItemFactory.byteType) {
+ fieldsWithStaticValues.put(field, DexValueByte.create((byte) cnst.getIntValue()));
+ } else if (fieldType == dexItemFactory.shortType) {
+ fieldsWithStaticValues.put(field, DexValueShort.create((short) cnst.getIntValue()));
+ } else if (fieldType == dexItemFactory.intType) {
+ fieldsWithStaticValues.put(field, DexValueInt.create(cnst.getIntValue()));
+ } else if (fieldType == dexItemFactory.longType) {
+ fieldsWithStaticValues.put(field, DexValueLong.create(cnst.getLongValue()));
+ } else if (fieldType == dexItemFactory.floatType) {
+ fieldsWithStaticValues.put(field, DexValueFloat.create(cnst.getFloatValue()));
+ } else if (fieldType == dexItemFactory.doubleType) {
+ fieldsWithStaticValues.put(field, DexValueDouble.create(cnst.getDoubleValue()));
+ } else if (fieldType == dexItemFactory.charType) {
+ fieldsWithStaticValues.put(field, DexValueChar.create((char) cnst.getIntValue()));
+ } else {
+ throw new Unreachable("Unexpected field type " + fieldType + ".");
+ }
}
- } else {
- ConstNumber cnst = inValue.getConstInstruction().asConstNumber();
- if (fieldType == dexItemFactory.booleanType) {
- fieldsWithStaticValues.put(field, DexValueBoolean.create(cnst.getBooleanValue()));
- } else if (fieldType == dexItemFactory.byteType) {
- fieldsWithStaticValues.put(field, DexValueByte.create((byte) cnst.getIntValue()));
- } else if (fieldType == dexItemFactory.shortType) {
- fieldsWithStaticValues.put(field, DexValueShort.create((short) cnst.getIntValue()));
- } else if (fieldType == dexItemFactory.intType) {
- fieldsWithStaticValues.put(field, DexValueInt.create(cnst.getIntValue()));
- } else if (fieldType == dexItemFactory.longType) {
- fieldsWithStaticValues.put(field, DexValueLong.create(cnst.getLongValue()));
- } else if (fieldType == dexItemFactory.floatType) {
- fieldsWithStaticValues.put(field, DexValueFloat.create(cnst.getFloatValue()));
- } else if (fieldType == dexItemFactory.doubleType) {
- fieldsWithStaticValues.put(field, DexValueDouble.create(cnst.getDoubleValue()));
- } else if (fieldType == dexItemFactory.charType) {
- fieldsWithStaticValues.put(field, DexValueChar.create((char) cnst.getIntValue()));
- } else {
- throw new Unreachable("Unexpected field type " + fieldType + ".");
+ } else if (appView.options().enableFieldTypePropagation && appView.appInfo().hasLiveness()) {
+ AppInfoWithLiveness appInfoWithLiveness = appView.withLiveness().appInfo();
+ if (appInfoWithLiveness.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)) {
+ TypeLatticeElement valueType = value.getTypeLattice();
+ assert valueType.strictlyLessThan(
+ TypeLatticeElement.fromDexType(fieldType, Nullability.maybeNull(), appView), appView);
+ feedback.markFieldHasDynamicType(field, valueType);
}
}
}
- // Remove the static put instructions now replaced by static field initial values.
- Set<Instruction> unnecessaryInstructions = Sets.newIdentityHashSet();
+ if (!unnecessaryStaticPuts.isEmpty()) {
+ // Remove the static put instructions now replaced by static field initial values.
+ Set<Instruction> unnecessaryInstructions = Sets.newIdentityHashSet();
- // Note: Traversing code.instructions(), and not unnecessaryStaticPuts(), to ensure
- // deterministic iteration order.
- InstructionListIterator instructionIterator = code.instructionListIterator();
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- if (!instruction.isStaticPut()
- || !unnecessaryStaticPuts.contains(instruction.asStaticPut())) {
- continue;
+ // Note: Traversing code.instructions(), and not unnecessaryStaticPuts(), to ensure
+ // deterministic iteration order.
+ InstructionListIterator instructionIterator = code.instructionListIterator();
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (!instruction.isStaticPut()
+ || !unnecessaryStaticPuts.contains(instruction.asStaticPut())) {
+ continue;
+ }
+ // Get a hold of the in-value.
+ Value inValue = instruction.asStaticPut().value();
+
+ // Remove the static-put instruction.
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+
+ // Collect, for removal, the instruction that created the value for the static put,
+ // if all users are gone. This is done even if these instructions can throw as for
+ // the current patterns matched these exceptions are not detectable.
+ if (inValue.numberOfAllUsers() > 0) {
+ continue;
+ }
+ if (inValue.isConstString()) {
+ unnecessaryInstructions.add(inValue.definition);
+ } else if (!inValue.isPhi() && inValue.definition.isInvokeVirtual()) {
+ unnecessaryInstructions.add(inValue.definition);
+ }
}
- // Get a hold of the in-value.
- Value inValue = instruction.asStaticPut().value();
- // Remove the static-put instruction.
- instructionIterator.removeOrReplaceByDebugLocalRead();
-
- // Collect, for removal, the instruction that created the value for the static put,
- // if all users are gone. This is done even if these instructions can throw as for
- // the current patterns matched these exceptions are not detectable.
- if (inValue.numberOfAllUsers() > 0) {
- continue;
+ // Remove the instructions collected for removal.
+ if (unnecessaryInstructions.size() > 0) {
+ IteratorUtils.removeIf(code.instructionListIterator(), unnecessaryInstructions::contains);
}
- if (inValue.isConstString()) {
- unnecessaryInstructions.add(inValue.definition);
- } else if (!inValue.isPhi() && inValue.definition.isInvokeVirtual()) {
- unnecessaryInstructions.add(inValue.definition);
- }
- }
-
- // Remove the instructions collected for removal.
- if (unnecessaryInstructions.size() > 0) {
- IteratorUtils.removeIf(code.instructionListIterator(), unnecessaryInstructions::contains);
}
// If we are in R8, and we have removed all static-put instructions to some field, then record
@@ -208,6 +224,7 @@
// First collect all the candidate fields that are *potentially* no longer being written to.
Set<DexField> candidates =
finalFieldPuts.stream()
+ .filter(unnecessaryStaticPuts::contains)
.map(FieldInstruction::getField)
.map(appInfoWithLiveness::resolveField)
.filter(appInfoWithLiveness::isStaticFieldWrittenOnlyInEnclosingStaticInitializer)
@@ -350,17 +367,19 @@
return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
}
DexField field = put.getField();
+ Value value = put.value();
+ TypeLatticeElement valueType = value.getTypeLattice();
if (clazz.definesStaticField(field)) {
if (isReadBefore.contains(field)) {
// Promoting this put to a class constant would cause a previous static-get
// instruction to read a different value.
continue;
}
- if (put.value().isDexItemBasedConstStringThatNeedsToComputeClassName()) {
+ if (value.isDexItemBasedConstStringThatNeedsToComputeClassName()) {
continue;
}
- if (put.value().isConstant()) {
- if (field.type.isReferenceType() && put.value().isZero()) {
+ if (value.isConstant()) {
+ if (field.type.isReferenceType() && value.isZero()) {
finalFieldPuts.put(field, put);
unnecessaryStaticPuts.add(put);
// If this field has been written before, those static-put's up to this point are
@@ -393,6 +412,9 @@
isWrittenBefore.remove(field);
}
continue;
+ } else if (valueType.isReference() && valueType.isDefinitelyNotNull()) {
+ finalFieldPuts.put(field, put);
+ continue;
}
// static-put that is reaching here can be redundant if the corresponding field is
// rewritten with another constant (of course before being read).
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 1a1aacb..1b36254 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -17,6 +18,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,12 +57,14 @@
InstructionListIterator instructionIterator = block.listIterator(code);
while (instructionIterator.hasNext()) {
Instruction current = instructionIterator.next();
+ Value outValue = current.outValue();
+ if (outValue == null) {
+ continue;
+ }
+
+ TypeLatticeElement dynamicType;
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- Value outValue = invoke.outValue();
- if (outValue == null) {
- continue;
- }
DexType staticReturnTypeRaw = invoke.getInvokedMethod().proto.returnType;
if (!staticReturnTypeRaw.isReferenceType()) {
@@ -78,33 +82,44 @@
continue;
}
- TypeLatticeElement dynamicReturnType = optimizationInfo.getDynamicReturnType();
- if (dynamicReturnType == null
- || !dynamicReturnType.strictlyLessThan(outValue.getTypeLattice(), appView)) {
+ dynamicType = optimizationInfo.getDynamicReturnType();
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ DexEncodedField encodedField = appView.appInfo().resolveField(staticGet.getField());
+ if (encodedField == null) {
continue;
}
- // Split block if needed (only debug instructions are allowed after the throwing
- // instruction, if any).
- BasicBlock insertionBlock =
- block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
+ dynamicType = encodedField.getOptimizationInfo().getDynamicType();
+ } else {
+ continue;
+ }
- // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
- Value specializedOutValue =
- code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
- outValue.replaceUsers(specializedOutValue);
+ if (dynamicType == null
+ || !dynamicType.strictlyLessThan(outValue.getTypeLattice(), appView)) {
+ continue;
+ }
- // Insert AssumeDynamicType instruction.
- Assume<DynamicTypeAssumption> assumeInstruction =
- Assume.createAssumeDynamicTypeInstruction(
- dynamicReturnType, specializedOutValue, outValue, invoke, appView);
- assumeInstruction.setPosition(
- appView.options().debug ? invoke.getPosition() : Position.none());
- if (insertionBlock == block) {
- instructionIterator.add(assumeInstruction);
- } else {
- insertionBlock.listIterator(code).add(assumeInstruction);
- }
+ // Split block if needed (only debug instructions are allowed after the throwing
+ // instruction, if any).
+ BasicBlock insertionBlock =
+ block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
+
+ // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
+ Value specializedOutValue =
+ code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+ outValue.replaceUsers(specializedOutValue);
+
+ // Insert AssumeDynamicType instruction.
+ Assume<DynamicTypeAssumption> assumeInstruction =
+ Assume.createAssumeDynamicTypeInstruction(
+ dynamicType, specializedOutValue, outValue, current, appView);
+ assumeInstruction.setPosition(
+ appView.options().debug ? current.getPosition() : Position.none());
+ if (insertionBlock == block) {
+ instructionIterator.add(assumeInstruction);
+ } else {
+ insertionBlock.listIterator(code).add(assumeInstruction);
}
}
}
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 e9dae85..17fea4b 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
@@ -368,46 +368,56 @@
}
// Check if a this value is known const.
- if (!appView.appInfo().isPinned(target.field)) {
- ConstInstruction replacement = target.valueAsConstInstruction(code, current.dest(), appView);
- if (replacement != null) {
- affectedValues.addAll(current.outValue().affectedValues());
- iterator.replaceCurrentInstruction(replacement);
- if (replacement.isDexItemBasedConstString()) {
- code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
- }
- feedback.markFieldAsPropagated(target);
- return;
- }
+ if (appView.appInfo().isPinned(target.field)) {
+ return;
}
- if (current.dest() != null) {
- // In case the class holder of this static field satisfying following criteria:
- // -- cannot trigger other static initializer except for its own
- // -- is final
- // -- has a class initializer which is classified as trivial
- // (see CodeRewriter::computeClassInitializerInfo) and
- // initializes the field being accessed
- //
- // ... and the field itself is not pinned by keep rules (in which case it might
- // be updated outside the class constructor, e.g. via reflections), it is safe
- // to assume that the static-get instruction reads the value it initialized value
- // in class initializer and is never null.
- DexClass holderDefinition = appView.definitionFor(field.holder);
- if (holderDefinition != null
- && holderDefinition.accessFlags.isFinal()
- && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) {
- Value outValue = current.dest();
- DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
- if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
- TrivialInitializer info =
- classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
- if (info != null
- && ((TrivialClassInitializer) info).field == field
- && !appView.appInfo().isPinned(field)
- && outValue.getTypeLattice().isReference()
- && outValue.canBeNull()) {
- insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+ ConstInstruction replacement = target.valueAsConstInstruction(code, current.dest(), appView);
+ if (replacement != null) {
+ affectedValues.addAll(current.outValue().affectedValues());
+ iterator.replaceCurrentInstruction(replacement);
+ if (replacement.isDexItemBasedConstString()) {
+ code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+ }
+ feedback.markFieldAsPropagated(target);
+ return;
+ }
+
+ if (current.hasOutValue()) {
+ Value outValue = current.outValue();
+ TypeLatticeElement outType = outValue.getTypeLattice();
+ if (outType.isReference() && outType.isNullable()) {
+ TypeLatticeElement dynamicType = target.getOptimizationInfo().getDynamicType();
+ if (dynamicType != null && dynamicType.isDefinitelyNotNull()) {
+ insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+ return;
+ }
+
+ // In case the class holder of this static field satisfying following criteria:
+ // -- cannot trigger other static initializer except for its own
+ // -- is final
+ // -- has a class initializer which is classified as trivial
+ // (see CodeRewriter::computeClassInitializerInfo) and
+ // initializes the field being accessed
+ //
+ // ... and the field itself is not pinned by keep rules (in which case it might
+ // be updated outside the class constructor, e.g. via reflections), it is safe
+ // to assume that the static-get instruction reads the value it initialized value
+ // in class initializer and is never null.
+ DexClass holderDefinition = appView.definitionFor(field.holder);
+ if (holderDefinition != null
+ && holderDefinition.accessFlags.isFinal()
+ && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) {
+ DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+ if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+ TrivialInitializer info =
+ classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+ if (info != null
+ && ((TrivialClassInitializer) info).field == field
+ && outValue.getTypeLattice().isReference()
+ && outValue.canBeNull()) {
+ insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 9b1b0df..50c16fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
private static final DefaultFieldOptimizationInfo INSTANCE = new DefaultFieldOptimizationInfo();
@@ -25,6 +27,11 @@
}
@Override
+ public TypeLatticeElement getDynamicType() {
+ return null;
+ }
+
+ @Override
public boolean valueHasBeenPropagated() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 3f5983e..4e9ef49 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -4,12 +4,16 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
public abstract class FieldOptimizationInfo {
public abstract MutableFieldOptimizationInfo mutableCopy();
public abstract boolean cannotBeKept();
+ public abstract TypeLatticeElement getDynamicType();
+
public abstract boolean valueHasBeenPropagated();
public boolean isDefaultFieldOptimizationInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 2b6ae1b..ed26f85 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
/**
* Optimization info for fields.
*
@@ -15,6 +17,7 @@
private boolean cannotBeKept = false;
private boolean valueHasBeenPropagated = false;
+ private TypeLatticeElement dynamicType = null;
@Override
public MutableFieldOptimizationInfo mutableCopy() {
@@ -34,6 +37,15 @@
}
@Override
+ public TypeLatticeElement getDynamicType() {
+ return dynamicType;
+ }
+
+ public void setDynamicType(TypeLatticeElement type) {
+ dynamicType = type;
+ }
+
+ @Override
public boolean valueHasBeenPropagated() {
return valueHasBeenPropagated;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index e454b27..d5a2646 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -83,6 +83,11 @@
getFieldOptimizationInfoForUpdating(field).markAsPropagated();
}
+ @Override
+ public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
+ getFieldOptimizationInfoForUpdating(field).setDynamicType(type);
+ }
+
// METHOD OPTIMIZATION INFO:
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 1d45828..4840e15 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -34,6 +34,9 @@
@Override
public void markFieldAsPropagated(DexEncodedField field) {}
+ @Override
+ public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {}
+
// METHOD OPTIMIZATION INFO:
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 4cee63d..af7a717 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -38,6 +38,11 @@
field.getMutableOptimizationInfo().markAsPropagated();
}
+ @Override
+ public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
+ // Ignored.
+ }
+
// METHOD OPTIMIZATION INFO.
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d3e5566..6c01902 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -237,6 +237,7 @@
public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
public boolean enableRedundantFieldLoadElimination = true;
public boolean enableValuePropagation = true;
+ public boolean enableFieldTypePropagation = true;
public boolean enableUninstantiatedTypeOptimization = true;
// TODO(b/138917494): Disable until we have numbers on potential performance penalties.
public boolean enableRedundantConstNumberOptimization = false;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 76b0115..9f20f9e 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -16,13 +16,9 @@
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -31,11 +27,9 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
-import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import org.junit.Rule;
@@ -45,7 +39,8 @@
public abstract class RunExamplesJava9Test
<B extends BaseCommand.Builder<? extends BaseCommand, B>> {
- static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR;
+
+ private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR;
abstract class TestRunner<C extends TestRunner<C>> {
final String testName;
@@ -72,45 +67,11 @@
return self();
}
- C withClassCheck(Consumer<FoundClassSubject> check) {
- return withDexCheck(inspector -> inspector.forAllClasses(check));
- }
-
- C withMethodCheck(Consumer<FoundMethodSubject> check) {
- return withClassCheck(clazz -> clazz.forAllMethods(check));
- }
-
C withArg(String arg) {
args.add(arg);
return self();
}
- <T extends InstructionSubject> C withInstructionCheck(
- Predicate<InstructionSubject> filter, Consumer<T> check) {
- return withMethodCheck(method -> {
- if (method.isAbstract()) {
- return;
- }
- Iterator<T> iterator = method.iterateInstructions(filter);
- while (iterator.hasNext()) {
- check.accept(iterator.next());
- }
- });
- }
-
- C withOptionConsumer(Consumer<InternalOptions> consumer) {
- optionConsumers.add(consumer);
- return self();
- }
-
- C withInterfaceMethodDesugaring(OffOrAuto behavior) {
- return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
- }
-
- C withTryWithResourcesDesugaring(OffOrAuto behavior) {
- return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
- }
-
void combinedOptionConsumer(InternalOptions options) {
for (Consumer<InternalOptions> consumer : optionConsumers) {
consumer.accept(options);
@@ -122,10 +83,6 @@
return self();
}
- C withMainDexClass(String... classes) {
- return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
- }
-
Path build() throws Throwable {
Path inputFile = getInputJar();
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -250,17 +207,17 @@
@Rule
public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
- boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+ private boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
return failsOn.containsKey(vmVersion)
&& failsOn.get(vmVersion).contains(name);
}
- boolean expectedToFail(String name) {
+ private boolean expectedToFail(String name) {
return failsOn(failsOn, name);
}
- boolean minSdkErrorExpected(String testName) {
+ private boolean minSdkErrorExpected(String testName) {
return minSdkErrorExpected.contains(testName);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
similarity index 81%
rename from src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
index 81ba319..e131f13 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
@@ -6,8 +6,9 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -21,7 +22,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class NonNullFieldTest extends TestBase {
+public class NonNullStaticFieldTest extends TestBase {
private final TestParameters parameters;
@@ -30,7 +31,7 @@
return getTestParameters().withAllRuntimes().build();
}
- public NonNullFieldTest(TestParameters parameters) {
+ public NonNullStaticFieldTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -39,6 +40,7 @@
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class)
.addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
.setMinApi(parameters.getRuntime())
.compile()
.inspect(this::verifyMainIsEmpty)
@@ -52,8 +54,7 @@
MethodSubject mainMethodSubject = testClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
- // TODO(b/140161397): Should propagate the fact that `field` is guaranteed to be non-null.
- assertFalse(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
+ assertTrue(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
}
static class TestClass {
@@ -62,8 +63,13 @@
public static void main(String[] args) {
if (field == null) {
- throw new NullPointerException();
+ dead();
}
}
+
+ @NeverInline
+ static void dead() {
+ System.out.println("Unreachable!");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
similarity index 72%
copy from src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
copy to src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
index 81ba319..7cf1f42 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
@@ -6,8 +6,10 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -21,7 +23,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class NonNullFieldTest extends TestBase {
+public class StaticFieldWithRefinedTypeTest extends TestBase {
private final TestParameters parameters;
@@ -30,15 +32,17 @@
return getTestParameters().withAllRuntimes().build();
}
- public NonNullFieldTest(TestParameters parameters) {
+ public StaticFieldWithRefinedTypeTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class)
+ .addInnerClasses(StaticFieldWithRefinedTypeTest.class)
.addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
.setMinApi(parameters.getRuntime())
.compile()
.inspect(this::verifyMainIsEmpty)
@@ -52,18 +56,27 @@
MethodSubject mainMethodSubject = testClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
- // TODO(b/140161397): Should propagate the fact that `field` is guaranteed to be non-null.
- assertFalse(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
+ assertTrue(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
}
static class TestClass {
- static Object field = new Object();
+ static A field = new B();
public static void main(String[] args) {
- if (field == null) {
- throw new NullPointerException();
+ if (!(field instanceof B)) {
+ dead();
}
}
+
+ @NeverInline
+ static void dead() {
+ System.out.println("Unreachable!");
+ }
}
+
+ @NeverMerge
+ static class A {}
+
+ static class B extends A {}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index b76a4fb..b32bc04 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -15,6 +14,7 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.google.common.base.Predicates;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
@@ -40,16 +40,14 @@
assertThat(inspector.clazz("class_staticizer.Regular$Companion"), isPresent());
assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
+ // The Util class is there, but its instance methods have been inlined.
ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
assertThat(utilClass, isPresent());
AtomicInteger nonStaticMethodCount = new AtomicInteger();
- utilClass.forAllMethods(
- method -> {
- if (!method.isStatic()) {
- nonStaticMethodCount.incrementAndGet();
- }
- });
- assertEquals(4, nonStaticMethodCount.get());
+ assertTrue(
+ utilClass.allMethods().stream()
+ .filter(Predicates.not(FoundMethodSubject::isStatic))
+ .allMatch(FoundMethodSubject::isInstanceInitializer));
});
// With class staticizer.
@@ -77,7 +75,6 @@
options -> {
options.enableClassInlining = false;
options.enableClassStaticizer = enabled;
- options.enableInliningOfInvokesWithNullableReceivers = false;
},
inspector);
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 7f038cc..fcb2872 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -89,9 +89,6 @@
o.enableClassStaticizer = false;
};
- private Consumer<InternalOptions> disableInliningOfInvokesWithNullableReceivers =
- o -> o.enableInliningOfInvokesWithNullableReceivers = false;
-
public R8KotlinPropertiesTest(
KotlinTargetVersion targetVersion, boolean allowAccessModification) {
super(targetVersion, allowAccessModification);
@@ -416,7 +413,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+ disableAggressiveClassOptimizations,
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -426,21 +423,19 @@
String propertyName = "primitiveProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
- // Getter and setter cannot be inlined because we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(companionClass, getter);
- checkMethodIsKept(companionClass, setter);
if (allowAccessModification) {
assertTrue(fieldSubject.getField().accessFlags.isPublic());
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
+
+ MemberNaming.MethodSignature getter =
+ COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, getter);
+
+ MemberNaming.MethodSignature setter =
+ COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, setter);
});
}
@@ -489,7 +484,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+ disableAggressiveClassOptimizations,
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -499,21 +494,18 @@
String propertyName = "internalProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
- // Getter and setter cannot be inlined because we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(companionClass, getter);
- checkMethodIsKept(companionClass, setter);
if (allowAccessModification) {
assertTrue(fieldSubject.getField().accessFlags.isPublic());
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
+
+ MemberNaming.MethodSignature getter =
+ COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, getter);
+ MemberNaming.MethodSignature setter =
+ COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, setter);
});
}
@@ -524,7 +516,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+ disableAggressiveClassOptimizations,
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -534,21 +526,19 @@
String propertyName = "publicProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
- MemberNaming.MethodSignature getter =
- COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter =
- COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
- // Getter and setter cannot be inlined because we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(companionClass, getter);
- checkMethodIsKept(companionClass, setter);
if (allowAccessModification) {
assertTrue(fieldSubject.getField().accessFlags.isPublic());
} else {
assertTrue(fieldSubject.getField().accessFlags.isPrivate());
}
+
+ MemberNaming.MethodSignature getter =
+ COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, getter);
+
+ MemberNaming.MethodSignature setter =
+ COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, setter);
});
}
@@ -596,7 +586,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+ disableAggressiveClassOptimizations,
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -605,15 +595,13 @@
String propertyName = "internalLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, getter);
- // Getter and setter cannot be inlined because we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(companionClass, getter);
- checkMethodIsKept(companionClass, setter);
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, setter);
});
}
@@ -625,7 +613,7 @@
runTest(
PACKAGE_NAME,
mainClass,
- disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+ disableAggressiveClassOptimizations,
app -> {
CodeInspector codeInspector = new CodeInspector(app);
ClassSubject outerClass =
@@ -634,15 +622,13 @@
String propertyName = "publicLateInitProp";
FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
assertTrue(fieldSubject.getField().accessFlags.isStatic());
+ assertTrue(fieldSubject.getField().accessFlags.isPublic());
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
- MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, getter);
- // Getter and setter cannot be inlined because we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(companionClass, getter);
- checkMethodIsKept(companionClass, setter);
- assertTrue(fieldSubject.getField().accessFlags.isPublic());
+ MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+ checkMethodIsRemoved(companionClass, setter);
});
}