blob: 0a66a30d0c0a7cf9b5b474ac29ac3975064c2c54 [file] [log] [blame]
// Copyright (c) 2017, 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.shaking;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
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.SingleNumberValue;
import com.android.tools.r8.ir.code.FieldGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.ObjectUtils;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Objects;
public class ProguardMemberRuleValue {
private enum Type {
BOOLEAN,
FIELD,
NULLABILITY,
STRING,
VALUE_RANGE
}
private final Type type;
private final boolean booleanValue;
private final LongInterval longInterval;
private final DexType fieldHolder;
private final DexString fieldName;
private final Nullability nullability;
private final DexString stringValue;
ProguardMemberRuleValue(boolean value) {
this.type = Type.BOOLEAN;
this.booleanValue = value;
this.longInterval = null;
this.fieldHolder = null;
this.fieldName = null;
this.nullability = null;
this.stringValue = null;
}
ProguardMemberRuleValue(DexString stringValue) {
this.type = Type.STRING;
this.booleanValue = false;
this.longInterval = null;
this.fieldHolder = null;
this.fieldName = null;
this.nullability = Nullability.definitelyNotNull();
this.stringValue = stringValue;
}
@SuppressWarnings("InconsistentOverloads")
ProguardMemberRuleValue(DexType fieldHolder, DexString fieldName, Nullability nullability) {
assert !nullability.isDefinitelyNull();
this.type = Type.FIELD;
this.booleanValue = false;
this.longInterval = null;
this.fieldHolder = fieldHolder;
this.fieldName = fieldName;
this.nullability = nullability;
this.stringValue = null;
}
ProguardMemberRuleValue(Nullability nullability) {
assert nullability.isDefinitelyNull() || nullability.isDefinitelyNotNull();
this.type = Type.NULLABILITY;
this.booleanValue = false;
this.longInterval = null;
this.fieldHolder = null;
this.fieldName = null;
this.nullability = nullability;
this.stringValue = null;
}
ProguardMemberRuleValue(LongInterval value) {
this.type = Type.VALUE_RANGE;
this.booleanValue = false;
this.longInterval = value;
this.fieldHolder = null;
this.fieldName = null;
this.nullability = getNullabilityForValueRange(value);
this.stringValue = null;
}
private static Nullability getNullabilityForValueRange(LongInterval value) {
if (value.isSingleValue(0)) {
return Nullability.definitelyNull();
} else if (!value.containsValue(0)) {
return Nullability.definitelyNotNull();
} else {
return Nullability.maybeNull();
}
}
public boolean isBoolean() {
return type == Type.BOOLEAN;
}
public boolean isField() {
return type == Type.FIELD;
}
public boolean isNullability() {
return type == Type.NULLABILITY;
}
public boolean isString() {
return type == Type.STRING;
}
public boolean isValueRange() {
return type == Type.VALUE_RANGE;
}
public boolean getBoolean() {
assert isBoolean();
return booleanValue;
}
public DexType getFieldHolder() {
assert isField();
return fieldHolder;
}
public DexString getFieldName() {
assert isField();
return fieldName;
}
public ProguardMemberRuleValue resolveFieldValue(AppView<?> appView, DexType valueType) {
AbstractValue value = toAbstractValue(appView, valueType);
if (valueType.isPrimitiveType()) {
if (!value.isSingleNumberValue()) {
return this;
}
SingleNumberValue singleNumberValue = value.asSingleNumberValue();
if (valueType.isBooleanType()) {
if (singleNumberValue.isSingleBoolean()) {
return new ProguardMemberRuleValue(singleNumberValue.isTrue());
}
} else if (valueType.isByteType()
|| valueType.isCharType()
|| valueType.isIntType()
|| valueType.isShortType()) {
return new ProguardMemberRuleValue(new LongInterval(singleNumberValue.getIntValue()));
} else if (valueType.isLongType()) {
return new ProguardMemberRuleValue(new LongInterval(singleNumberValue.getLongValue()));
} else {
assert valueType.isDoubleType() || valueType.isFloatType();
}
} else {
assert valueType.isReferenceType();
if (value.isNull()) {
return new ProguardMemberRuleValue(Nullability.definitelyNull());
} else if (value.isSingleStringValue()) {
return new ProguardMemberRuleValue(value.asSingleStringValue().getDexString());
}
}
return this;
}
private boolean hasNullability() {
return isField() || isNullability() || isString() || isValueRange();
}
public Nullability getNullability() {
assert hasNullability();
return nullability;
}
public DexString getString() {
assert isString();
return stringValue;
}
public LongInterval getValueRange() {
assert isValueRange();
return longInterval;
}
public AbstractValue toAbstractValue(AppView<?> appView, DexType valueType) {
AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
switch (type) {
case BOOLEAN:
return abstractValueFactory.createSingleNumberValue(
BooleanUtils.intValue(booleanValue), TypeElement.getBoolean());
case FIELD:
DexClass holder = appView.definitionFor(fieldHolder);
if (holder != null) {
DexEncodedField field = holder.lookupUniqueStaticFieldWithName(fieldName);
if (field != null) {
if (field.isFinal() && field.hasExplicitStaticValue()) {
AbstractValue abstractValue =
field.getStaticValue().toAbstractValue(abstractValueFactory);
if (abstractValue.isSingleValue()) {
return abstractValue;
}
}
return abstractValueFactory.createSingleStatelessFieldValue(field.getReference());
}
}
return AbstractValue.unknown();
case NULLABILITY:
return nullability.isDefinitelyNull()
? abstractValueFactory.createUncheckedNullValue()
: AbstractValue.unknown();
case STRING:
return abstractValueFactory.createSingleStringValue(stringValue);
case VALUE_RANGE:
if (valueType.isReferenceType()) {
assert false;
return AbstractValue.unknown();
}
return longInterval.isSingleValue()
? abstractValueFactory.createSingleNumberValue(
longInterval.getSingleValue(), TypeElement.getLong())
: abstractValueFactory.createNumberFromIntervalValue(
longInterval.getMin(), longInterval.getMax());
default:
throw new Unreachable("Unexpected type: " + type);
}
}
public DynamicType toDynamicType(AppView<?> appView, DexType valueType) {
return valueType.isReferenceType() && hasNullability() && getNullability().isDefinitelyNotNull()
? DynamicType.definitelyNotNull()
: DynamicType.unknown();
}
public boolean test(AppView<?> appView, Value value) {
switch (type) {
case BOOLEAN:
return value.isConstNumber(booleanValue ? 1 : 0);
case FIELD:
{
Value root = value.getAliasedValue();
// If the value is defined by a field-get instruction, then check if the field instruction
// matches the field in the condition.
if (root.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
FieldGet fieldGet = value.getDefinition().asFieldGet();
DexField field = fieldGet.getField();
if (field.getName().isIdenticalTo(getFieldName())) {
if (field.getHolderType().isIdenticalTo(getFieldHolder())) {
return true;
}
if (appView.hasClassHierarchy()) {
DexEncodedField resolvedField =
appView.appInfoWithClassHierarchy().resolveField(field).getResolvedField();
return resolvedField != null
&& resolvedField.getHolderType().isIdenticalTo(getFieldHolder());
}
}
return false;
}
// Otherwise lookup the field in the condition and check if it has a constant value.
// If so, check if the value matches the constant value.
if (appView.hasClassHierarchy()
&& root.isDefinedByInstructionSatisfying(Instruction::isConstInstruction)) {
DexClass fieldHolder = appView.definitionFor(getFieldHolder());
if (fieldHolder != null) {
List<DexEncodedField> fields =
Lists.newArrayList(
fieldHolder.fields(f -> f.getName().isIdenticalTo(getFieldName())));
if (fields.size() == 1) {
DexEncodedField field = fields.get(0);
AbstractValue fieldValue = field.getOptimizationInfo().getAbstractValue();
if (fieldValue.isSingleValue()) {
if (fieldValue.isNull()) {
return value.getType().isReferenceType()
&& value.getType().nullability().isDefinitelyNull();
} else if (fieldValue.isSingleConstClassValue()) {
return value.isConstClass(fieldValue.asSingleConstClassValue().getType());
} else if (fieldValue.isSingleNumberValue()) {
return value.isConstNumber(fieldValue.asSingleNumberValue().getValue());
} else if (fieldValue.isSingleStringValue()) {
return value.isConstString(fieldValue.asSingleStringValue().getDexString());
}
}
}
}
}
return false;
}
case NULLABILITY:
{
assert getNullability().isDefinitelyNotNull() || getNullability().isDefinitelyNull();
TypeElement type = value.getType();
return type.isReferenceType() && type.nullability() == getNullability();
}
case STRING:
return value.isConstString(stringValue);
case VALUE_RANGE:
{
if (value.isConstNumber()) {
long rawValue = value.getConstInstruction().asConstNumber().getRawValue();
return longInterval.containsValue(rawValue);
}
return false;
}
default:
throw new Unreachable("Unexpected type: " + type);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ProguardMemberRuleValue)) {
return false;
}
ProguardMemberRuleValue other = (ProguardMemberRuleValue) obj;
return type == other.type
&& booleanValue == other.booleanValue
&& Objects.equals(longInterval, other.longInterval)
&& ObjectUtils.identical(fieldHolder, other.fieldHolder)
&& ObjectUtils.identical(fieldName, other.fieldName)
&& nullability == other.nullability
&& ObjectUtils.identical(stringValue, other.stringValue);
}
@Override
public int hashCode() {
return ObjectUtils.hashZLLLLLL(
booleanValue, type, longInterval, fieldHolder, fieldName, nullability, stringValue);
}
@Override
public String toString() {
return " return " + getValueString();
}
public String getValueString() {
switch (type) {
case BOOLEAN:
return Boolean.toString(booleanValue);
case FIELD:
StringBuilder result = new StringBuilder();
if (nullability.isDefinitelyNotNull()) {
result.append("@NonNull ");
}
result.append(fieldHolder.getTypeName()).append('.').append(fieldName);
return result.toString();
case NULLABILITY:
return nullability.isDefinitelyNull() ? "null" : "@NonNull";
case STRING:
return stringValue.toString();
case VALUE_RANGE:
if (longInterval.isSingleValue()) {
return Long.toString(longInterval.getMin());
} else {
return longInterval.getMin() + ".." + longInterval.getMax();
}
default:
throw new Unreachable("Unexpected type: " + type);
}
}
}