blob: 08d4d61fcd3843e6865190abdec35a21e9e12e15 [file] [log] [blame]
// Copyright (c) 2016, 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.graph;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
public static final DexType[] EMPTY_ARRAY = {};
// Bundletool is merging classes that may originate from a build with an old version of R8.
// Allow merging of classes that use names from older versions of R8.
private static List<String> OLD_SYNTHESIZED_NAMES = ImmutableList.of("$r8$java8methods$utility");
public final DexString descriptor;
private String toStringCache = null;
DexType(DexString descriptor) {
assert !descriptor.toString().contains(".")
: "Malformed descriptor: " + descriptor.toString();
this.descriptor = descriptor;
}
@Override
public int computeHashCode() {
return descriptor.hashCode();
}
@Override
public boolean computeEquals(Object other) {
if (other instanceof DexType) {
return descriptor.equals(((DexType) other).descriptor);
}
return false;
}
public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
return classInitializationMayHaveSideEffects(
appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean classInitializationMayHaveSideEffects(
AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
DexClass clazz = appView.definitionFor(this);
return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen);
}
public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
return initializationOfParentTypesMayHaveSideEffects(
appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
}
public boolean initializationOfParentTypesMayHaveSideEffects(
AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
DexClass clazz = appView.definitionFor(this);
return clazz == null
|| clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
}
public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
if (isClassType()) {
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(this));
if (clazz == null) {
return false;
}
if (appView.options().enableUninstantiatedTypeOptimizationForInterfaces) {
return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
} else {
return !clazz.isInterface() && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
}
}
return false;
}
public boolean isSamePackage(DexType other) {
return getPackageDescriptor().equals(other.getPackageDescriptor());
}
public String toDescriptorString() {
return descriptor.toString();
}
public String toBinaryName() {
String descriptor = toDescriptorString();
assert descriptor.length() > 1
&& descriptor.charAt(0) == 'L'
&& descriptor.charAt(descriptor.length() - 1) == ';';
return descriptor.substring(1, descriptor.length() - 1);
}
@Override
public String toSourceString() {
if (toStringCache == null) {
// TODO(ager): Pass in a ProguardMapReader to map names back to original names.
if (DexItemFactory.isInternalSentinel(this)) {
toStringCache = descriptor.toString();
} else {
toStringCache = DescriptorUtils.descriptorToJavaType(toDescriptorString());
}
}
return toStringCache;
}
public char toShorty() {
char c = (char) descriptor.content[0];
return c == '[' ? 'L' : c;
}
@Override
public String toSmaliString() {
return toDescriptorString();
}
@Override
public String toString() {
return toSourceString();
}
@Override
public void collectIndexedItems(IndexedItemCollection collection,
DexMethod method, int instructionOffset) {
if (collection.addType(this)) {
collection.getRenamedDescriptor(this).collectIndexedItems(collection, method,
instructionOffset);
}
}
@Override
public void flushCachedValues() {
super.flushCachedValues();
toStringCache = null;
}
@Override
public int getOffset(ObjectToOffsetMapping mapping) {
return mapping.getOffsetFor(this);
}
@Override
public boolean isDexType() {
return true;
}
@Override
public DexType asDexType() {
return this;
}
@Override
public int compareTo(DexType other) {
return sortedCompareTo(other.getSortedIndex());
}
@Override
public int slowCompareTo(DexType other) {
return descriptor.slowCompareTo(other.descriptor);
}
@Override
public int slowCompareTo(DexType other, NamingLens namingLens) {
DexString thisDescriptor = namingLens.lookupDescriptor(this);
DexString otherDescriptor = namingLens.lookupDescriptor(other);
return thisDescriptor.slowCompareTo(otherDescriptor);
}
@Override
public int layeredCompareTo(DexType other, NamingLens namingLens) {
DexString thisDescriptor = namingLens.lookupDescriptor(this);
DexString otherDescriptor = namingLens.lookupDescriptor(other);
return thisDescriptor.compareTo(otherDescriptor);
}
public boolean isPrimitiveType() {
return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]);
}
public boolean isVoidType() {
return (char) descriptor.content[0] == 'V';
}
public boolean isBooleanType() {
return descriptor.content[0] == 'Z';
}
public boolean isByteType() {
return descriptor.content[0] == 'B';
}
public boolean isCharType() {
return descriptor.content[0] == 'C';
}
public boolean isShortType() {
return descriptor.content[0] == 'S';
}
public boolean isIntType() {
return descriptor.content[0] == 'I';
}
public boolean isFloatType() {
return descriptor.content[0] == 'F';
}
public boolean isLongType() {
return descriptor.content[0] == 'J';
}
public boolean isDoubleType() {
return descriptor.content[0] == 'D';
}
public boolean isArrayType() {
char firstChar = (char) descriptor.content[0];
return firstChar == '[';
}
public boolean isClassType() {
char firstChar = (char) descriptor.content[0];
return firstChar == 'L';
}
public boolean isReferenceType() {
boolean isReferenceType = isArrayType() || isClassType();
assert isReferenceType != isPrimitiveType() || isVoidType();
return isReferenceType;
}
public boolean isPrimitiveArrayType() {
if (!isArrayType()) {
return false;
}
return DescriptorUtils.isPrimitiveType((char) descriptor.content[1]);
}
public boolean isWideType() {
return isDoubleType() || isLongType();
}
public boolean isD8R8SynthesizedLambdaClassType() {
String name = toSourceString();
return name.contains(LAMBDA_CLASS_NAME_PREFIX);
}
public boolean isD8R8SynthesizedClassType() {
String name = toSourceString();
return name.contains(COMPANION_CLASS_NAME_SUFFIX)
|| name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
|| name.contains(DISPATCH_CLASS_NAME_SUFFIX)
|| name.contains(TYPE_WRAPPER_SUFFIX)
|| name.contains(VIVIFIED_TYPE_WRAPPER_SUFFIX)
|| name.contains(LAMBDA_CLASS_NAME_PREFIX)
|| name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
|| name.contains(OutlineOptions.CLASS_NAME)
|| name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
|| name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
|| name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
|| name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
|| oldSynthesizedName(name);
}
private boolean oldSynthesizedName(String name) {
for (String synthesizedPrefix : OLD_SYNTHESIZED_NAMES) {
if (name.contains(synthesizedPrefix)) {
return true;
}
}
return false;
}
public boolean isProgramType(DexDefinitionSupplier definitions) {
DexClass clazz = definitions.definitionFor(this);
return clazz != null && clazz.isProgramClass();
}
public boolean isResolvable(AppView<?> appView) {
DexClass clazz = appView.definitionFor(this);
return clazz != null && clazz.isResolvable(appView);
}
public int elementSizeForPrimitiveArrayType() {
assert isPrimitiveArrayType();
switch (descriptor.content[1]) {
case 'Z': // boolean
case 'B': // byte
return 1;
case 'S': // short
case 'C': // char
return 2;
case 'I': // int
case 'F': // float
return 4;
case 'J': // long
case 'D': // double
return 8;
default:
throw new Unreachable("Not array of primitives '" + descriptor + "'");
}
}
public int getNumberOfLeadingSquareBrackets() {
int leadingSquareBrackets = 0;
while (descriptor.content[leadingSquareBrackets] == '[') {
leadingSquareBrackets++;
}
return leadingSquareBrackets;
}
public DexType toBaseType(DexItemFactory dexItemFactory) {
int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
if (leadingSquareBrackets == 0) {
return this;
}
DexString newDesc = dexItemFactory.createString(descriptor.size - leadingSquareBrackets,
Arrays.copyOfRange(descriptor.content, leadingSquareBrackets, descriptor.content.length));
return dexItemFactory.createType(newDesc);
}
// Similar to the method above, but performs a lookup only, allowing to use
// this method also after strings are sorted in the ApplicationWriter.
public DexType lookupBaseType(DexItemFactory dexItemFactory) {
int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
if (leadingSquareBrackets == 0) {
return this;
}
DexString newDesc =
dexItemFactory.lookupString(
descriptor.size - leadingSquareBrackets,
Arrays.copyOfRange(
descriptor.content, leadingSquareBrackets, descriptor.content.length));
return dexItemFactory.lookupType(newDesc);
}
public DexType replaceBaseType(DexType newBase, DexItemFactory dexItemFactory) {
assert this.isArrayType();
assert !newBase.isArrayType();
return newBase.toArrayType(getNumberOfLeadingSquareBrackets(), dexItemFactory);
}
public DexType toArrayType(int dimensions, DexItemFactory dexItemFactory) {
byte[] content = new byte[descriptor.content.length + dimensions];
Arrays.fill(content, 0, dimensions, (byte) '[');
System.arraycopy(descriptor.content, 0, content, dimensions, descriptor.content.length);
DexString newDesc = dexItemFactory.createString(descriptor.size + dimensions, content);
return dexItemFactory.createType(newDesc);
}
public DexType toArrayElementType(DexItemFactory dexItemFactory) {
assert this.isArrayType();
DexString newDesc = dexItemFactory.createString(descriptor.size - 1,
Arrays.copyOfRange(descriptor.content, 1, descriptor.content.length));
return dexItemFactory.createType(newDesc);
}
private String getPackageOrName(boolean packagePart) {
assert isClassType();
String descriptor = toDescriptorString();
int lastSeparator = descriptor.lastIndexOf('/');
if (lastSeparator == -1) {
return packagePart ? "" : descriptor.substring(1, descriptor.length() - 1);
} else {
return packagePart ? descriptor.substring(1, lastSeparator)
: descriptor.substring(lastSeparator + 1, descriptor.length() - 1);
}
}
public String getPackageDescriptor() {
return getPackageOrName(true);
}
public String getName() {
if (isPrimitiveType()) {
return toSourceString();
}
return getPackageOrName(false);
}
/** Get the fully qualified name using '/' in place of '.', aka the "internal type name" in ASM */
public String getInternalName() {
assert isClassType() || isArrayType();
return DescriptorUtils.descriptorToInternalName(toDescriptorString());
}
public String getPackageName() {
return DescriptorUtils.getPackageNameFromBinaryName(toBinaryName());
}
public Pair<String, String> rewritingPrefixIn(Map<String, String> map) {
// TODO(b/134732760): Rewrite this to use descriptors and not Strings.
String javaClassName = this.toString();
for (String rewritePrefix : map.keySet()) {
if (javaClassName.startsWith(rewritePrefix)) {
return new Pair<>(rewritePrefix, map.get(rewritePrefix));
}
}
return null;
}
}