|  | // 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.synthesis; | 
|  |  | 
|  | import com.android.tools.r8.Version; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting; | 
|  | import com.android.tools.r8.references.ClassReference; | 
|  | import com.android.tools.r8.references.Reference; | 
|  | import com.android.tools.r8.utils.DescriptorUtils; | 
|  | import com.android.tools.r8.utils.structural.Equatable; | 
|  | import com.android.tools.r8.utils.structural.Ordered; | 
|  | import com.google.common.hash.Hasher; | 
|  | import com.google.common.hash.Hashing; | 
|  | import java.nio.charset.StandardCharsets; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.List; | 
|  |  | 
|  | public class SyntheticNaming { | 
|  |  | 
|  | private KindGenerator generator = new KindGenerator(); | 
|  |  | 
|  | // Global synthetics. | 
|  | public final SyntheticKind RECORD_TAG = generator.forGlobalClass(); | 
|  | public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass(); | 
|  |  | 
|  | // Classpath only synthetics in the global type namespace. | 
|  | public final SyntheticKind GENERIC_API_CONVERSION_STUB = generator.forGlobalClasspathClass(); | 
|  | public final SyntheticKind RETARGET_STUB = generator.forGlobalClasspathClass(); | 
|  | public final SyntheticKind EMULATED_INTERFACE_MARKER_CLASS = generator.forGlobalClasspathClass(); | 
|  |  | 
|  | // Fixed suffix synthetics. Each has a hygienic prefix type. | 
|  | public final SyntheticKind ENUM_UNBOXING_LOCAL_UTILITY_CLASS = | 
|  | generator.forFixedClass("$EnumUnboxingLocalUtility"); | 
|  | public final SyntheticKind ENUM_UNBOXING_SHARED_UTILITY_CLASS = | 
|  | generator.forFixedClass("$EnumUnboxingSharedUtility"); | 
|  | public final SyntheticKind COMPANION_CLASS = generator.forFixedClass("$-CC"); | 
|  | public final SyntheticKind EMULATED_INTERFACE_CLASS = | 
|  | generator.forFixedClass(InterfaceDesugaringForTesting.EMULATED_INTERFACE_CLASS_SUFFIX); | 
|  | public final SyntheticKind RETARGET_CLASS = generator.forFixedClass("RetargetClass"); | 
|  | public final SyntheticKind RETARGET_INTERFACE = generator.forFixedClass("RetargetInterface"); | 
|  | public final SyntheticKind WRAPPER = generator.forFixedClass("$Wrapper"); | 
|  | public final SyntheticKind VIVIFIED = generator.forFixedClass(""); | 
|  | public final SyntheticKind VIVIFIED_WRAPPER = generator.forFixedClass("$VivifiedWrapper"); | 
|  | public final SyntheticKind INIT_TYPE_ARGUMENT = generator.forFixedClass("-IA"); | 
|  | public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_1 = | 
|  | generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$1"); | 
|  | public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_2 = | 
|  | generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$2"); | 
|  | public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_3 = | 
|  | generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$3"); | 
|  | public final SyntheticKind ENUM_CONVERSION = generator.forFixedClass("$EnumConversion"); | 
|  |  | 
|  | // Locally generated synthetic classes. | 
|  | public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda"); | 
|  | public final SyntheticKind THREAD_LOCAL = generator.forInstanceClass("ThreadLocal"); | 
|  |  | 
|  | // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors. | 
|  | public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT = | 
|  | generator.forNonSharableInstanceClass("$IA"); | 
|  | public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass("$Condy"); | 
|  |  | 
|  | // Method synthetics. | 
|  | public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD = | 
|  | generator.forSingleMethod("CheckNotZero"); | 
|  | public final SyntheticKind RECORD_HELPER = generator.forSingleMethod("Record"); | 
|  | public final SyntheticKind BACKPORT = generator.forSingleMethod("Backport"); | 
|  | public final SyntheticKind BACKPORT_WITH_FORWARDING = | 
|  | generator.forSingleMethod("BackportWithForwarding"); | 
|  | public final SyntheticKind STATIC_INTERFACE_CALL = | 
|  | generator.forSingleMethod("StaticInterfaceCall"); | 
|  | public final SyntheticKind TO_STRING_IF_NOT_NULL = generator.forSingleMethod("ToStringIfNotNull"); | 
|  | public final SyntheticKind THROW_CCE_IF_NOT_NULL = generator.forSingleMethod("ThrowCCEIfNotNull"); | 
|  | public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE"); | 
|  | public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE"); | 
|  | public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME"); | 
|  | public final SyntheticKind THROW_RTE = generator.forSingleMethod("ThrowRTE"); | 
|  | public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource"); | 
|  | public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad"); | 
|  | public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline"); | 
|  | public final SyntheticKind COVARIANT_OUTLINE = generator.forSingleMethod("CovariantOutline"); | 
|  | public final SyntheticKind API_CONVERSION = generator.forSingleMethod("APIConversion"); | 
|  | public final SyntheticKind API_CONVERSION_PARAMETERS = | 
|  | generator.forSingleMethod("APIConversionParameters"); | 
|  | public final SyntheticKind COLLECTION_CONVERSION = | 
|  | generator.forSingleMethod("$CollectionConversion"); | 
|  | public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod("ApiModelOutline"); | 
|  |  | 
|  | private final List<SyntheticKind> ALL_KINDS; | 
|  | private String lazyVersionHash = null; | 
|  |  | 
|  | public SyntheticNaming() { | 
|  | ALL_KINDS = generator.getAllKinds(); | 
|  | generator = null; | 
|  | } | 
|  |  | 
|  | public String getVersionHash() { | 
|  | if (lazyVersionHash == null) { | 
|  | computeVersionHash(); | 
|  | } | 
|  | return lazyVersionHash; | 
|  | } | 
|  |  | 
|  | private void computeVersionHash() { | 
|  | Hasher hasher = Hashing.sha256().newHasher(); | 
|  | hasher.putString(Version.getVersionString(), StandardCharsets.UTF_8); | 
|  | for (SyntheticKind kind : ALL_KINDS) { | 
|  | kind.hash(hasher); | 
|  | } | 
|  | lazyVersionHash = hasher.hash().toString(); | 
|  | } | 
|  |  | 
|  | public Collection<SyntheticKind> kinds() { | 
|  | return ALL_KINDS; | 
|  | } | 
|  |  | 
|  | public SyntheticKind fromId(int id) { | 
|  | if (0 < id && id <= ALL_KINDS.size()) { | 
|  | return ALL_KINDS.get(id - 1); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private static class KindGenerator { | 
|  | private int nextId = 1; | 
|  | private List<SyntheticKind> kinds = new ArrayList<>(); | 
|  |  | 
|  | private SyntheticKind register(SyntheticKind kind) { | 
|  | kinds.add(kind); | 
|  | if (kinds.size() != kind.getId()) { | 
|  | throw new Unreachable("Invalid synthetic kind id: " + kind.getId()); | 
|  | } | 
|  | return kind; | 
|  | } | 
|  |  | 
|  | private int getNextId() { | 
|  | return nextId++; | 
|  | } | 
|  |  | 
|  | SyntheticKind forSingleMethod(String descriptor) { | 
|  | return register(new SyntheticMethodKind(getNextId(), descriptor)); | 
|  | } | 
|  |  | 
|  | // TODO(b/214901256): Remove once fixed. | 
|  | SyntheticKind forNonSharableInstanceClass(String descriptor) { | 
|  | return register(new SyntheticClassKind(getNextId(), descriptor, false)); | 
|  | } | 
|  |  | 
|  | SyntheticKind forInstanceClass(String descriptor) { | 
|  | return register(new SyntheticClassKind(getNextId(), descriptor, true)); | 
|  | } | 
|  |  | 
|  | SyntheticKind forFixedClass(String descriptor) { | 
|  | return register(new SyntheticFixedClassKind(getNextId(), descriptor, false)); | 
|  | } | 
|  |  | 
|  | SyntheticKind forGlobalClass() { | 
|  | return register(new SyntheticFixedClassKind(getNextId(), "", true)); | 
|  | } | 
|  |  | 
|  | SyntheticKind forGlobalClasspathClass() { | 
|  | return register(new SyntheticFixedClassKind(getNextId(), "", false)); | 
|  | } | 
|  |  | 
|  | List<SyntheticKind> getAllKinds() { | 
|  | List<SyntheticKind> kinds = this.kinds; | 
|  | this.kinds = null; | 
|  | return kinds; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Enumeration of all kinds of synthetic items. | 
|  | * | 
|  | * <p>The synthetic kinds are used to provide hinting about what a synthetic item represents. The | 
|  | * kinds must *not* be used be the compiler and are only meant for "debugging". The compiler and | 
|  | * its test may use the kind information as part of asserting properties of the compiler. The kind | 
|  | * will be put into any non-minified synthetic name and thus the kind "descriptor" must be a | 
|  | * distinct for each kind. | 
|  | */ | 
|  | public abstract static class SyntheticKind implements Ordered<SyntheticKind> { | 
|  |  | 
|  | private final int id; | 
|  | private final String descriptor; | 
|  |  | 
|  | SyntheticKind(int id, String descriptor) { | 
|  | this.id = id; | 
|  | this.descriptor = descriptor; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int compareTo(SyntheticKind other) { | 
|  | return Integer.compare(id, other.getId()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return id; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | return Equatable.equalsImpl(this, o); | 
|  | } | 
|  |  | 
|  | public int getId() { | 
|  | return id; | 
|  | } | 
|  |  | 
|  | public String getDescriptor() { | 
|  | return descriptor; | 
|  | } | 
|  |  | 
|  | public abstract boolean isShareable(); | 
|  |  | 
|  | public abstract boolean isSingleSyntheticMethod(); | 
|  |  | 
|  | public abstract boolean isFixedSuffixSynthetic(); | 
|  |  | 
|  | public abstract boolean isGlobal(); | 
|  |  | 
|  | public abstract boolean isMayOverridesNonProgramType(); | 
|  |  | 
|  | public final void hash(Hasher hasher) { | 
|  | hasher.putInt(getId()); | 
|  | hasher.putString(getDescriptor(), StandardCharsets.UTF_8); | 
|  | internalHash(hasher); | 
|  | } | 
|  |  | 
|  | public abstract void internalHash(Hasher hasher); | 
|  | } | 
|  |  | 
|  | private static class SyntheticMethodKind extends SyntheticKind { | 
|  |  | 
|  | public SyntheticMethodKind(int id, String descriptor) { | 
|  | super(id, descriptor); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isShareable() { | 
|  | // Single methods may always be shared. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isSingleSyntheticMethod() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isFixedSuffixSynthetic() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isGlobal() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isMayOverridesNonProgramType() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void internalHash(Hasher hasher) { | 
|  | hasher.putString("method", StandardCharsets.UTF_8); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class SyntheticClassKind extends SyntheticKind { | 
|  |  | 
|  | // TODO(b/214901256): Remove once fixed. | 
|  | private final boolean sharable; | 
|  |  | 
|  | public SyntheticClassKind(int id, String descriptor, boolean sharable) { | 
|  | super(id, descriptor); | 
|  | this.sharable = sharable; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isShareable() { | 
|  | return sharable; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final boolean isSingleSyntheticMethod() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isFixedSuffixSynthetic() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isGlobal() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isMayOverridesNonProgramType() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void internalHash(Hasher hasher) { | 
|  | hasher.putString("class", StandardCharsets.UTF_8); | 
|  | hasher.putBoolean(sharable); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class SyntheticFixedClassKind extends SyntheticClassKind { | 
|  | private final boolean mayOverridesNonProgramType; | 
|  |  | 
|  | private SyntheticFixedClassKind(int id, String descriptor, boolean mayOverridesNonProgramType) { | 
|  | super(id, descriptor, false); | 
|  | this.mayOverridesNonProgramType = mayOverridesNonProgramType; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isShareable() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isFixedSuffixSynthetic() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isGlobal() { | 
|  | return getDescriptor().isEmpty(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isMayOverridesNonProgramType() { | 
|  | return mayOverridesNonProgramType; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void internalHash(Hasher hasher) { | 
|  | hasher.putString(isGlobal() ? "global" : "fixed", StandardCharsets.UTF_8); | 
|  | hasher.putBoolean(mayOverridesNonProgramType); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final String SYNTHETIC_CLASS_SEPARATOR = "$$"; | 
|  | /** | 
|  | * The internal synthetic class separator is only used for representing synthetic items during | 
|  | * compilation. In particular, this separator must never be used to write synthetic classes to the | 
|  | * final compilation result. | 
|  | */ | 
|  | private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = | 
|  | SYNTHETIC_CLASS_SEPARATOR + "InternalSynthetic"; | 
|  | /** | 
|  | * The external synthetic class separator is used when writing classes. It may appear in types | 
|  | * during compilation as the output of a compilation may be the input to another. | 
|  | */ | 
|  | private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = | 
|  | SYNTHETIC_CLASS_SEPARATOR + "ExternalSynthetic"; | 
|  | /** Method name when generating synthetic methods in a class. */ | 
|  | static final String INTERNAL_SYNTHETIC_METHOD_NAME = "m"; | 
|  |  | 
|  | static String getPrefixForExternalSyntheticType(SyntheticKind kind, DexType type) { | 
|  | String binaryName = type.toBinaryName(); | 
|  | if (kind.isGlobal()) { | 
|  | return binaryName; | 
|  | } | 
|  | int index = | 
|  | binaryName.lastIndexOf( | 
|  | kind.isFixedSuffixSynthetic() ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR); | 
|  | if (index < 0) { | 
|  | throw new Unreachable("Unexpected failure to compute a synthetic prefix for " + binaryName); | 
|  | } | 
|  | return binaryName.substring(0, index); | 
|  | } | 
|  |  | 
|  | static DexType createFixedType( | 
|  | SyntheticKind kind, SynthesizingContext context, DexItemFactory factory) { | 
|  | assert kind.isFixedSuffixSynthetic(); | 
|  | return createType("", kind, context.getSynthesizingContextType(), "", factory); | 
|  | } | 
|  |  | 
|  | static DexType createInternalType( | 
|  | SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) { | 
|  | assert !kind.isFixedSuffixSynthetic(); | 
|  | return createType( | 
|  | INTERNAL_SYNTHETIC_CLASS_SEPARATOR, | 
|  | kind, | 
|  | context.getSynthesizingContextType(), | 
|  | id, | 
|  | factory); | 
|  | } | 
|  |  | 
|  | static DexType createExternalType( | 
|  | SyntheticKind kind, String externalSyntheticTypePrefix, String id, DexItemFactory factory) { | 
|  | assert kind.isFixedSuffixSynthetic() == id.isEmpty(); | 
|  | return createType( | 
|  | kind.isFixedSuffixSynthetic() ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, | 
|  | kind, | 
|  | externalSyntheticTypePrefix, | 
|  | id, | 
|  | factory); | 
|  | } | 
|  |  | 
|  | private static DexType createType( | 
|  | String separator, SyntheticKind kind, DexType context, String id, DexItemFactory factory) { | 
|  | return factory.createType(createDescriptor(separator, kind, context.getInternalName(), id)); | 
|  | } | 
|  |  | 
|  | private static DexType createType( | 
|  | String separator, | 
|  | SyntheticKind kind, | 
|  | String externalSyntheticTypePrefix, | 
|  | String id, | 
|  | DexItemFactory factory) { | 
|  | return factory.createType(createDescriptor(separator, kind, externalSyntheticTypePrefix, id)); | 
|  | } | 
|  |  | 
|  | private static String createDescriptor( | 
|  | String separator, SyntheticKind kind, String externalSyntheticTypePrefix, String id) { | 
|  | return DescriptorUtils.getDescriptorFromClassBinaryName( | 
|  | externalSyntheticTypePrefix + separator + kind.descriptor + id); | 
|  | } | 
|  |  | 
|  | public static boolean verifyNotInternalSynthetic(DexType type) { | 
|  | return verifyNotInternalSynthetic(type.toDescriptorString()); | 
|  | } | 
|  |  | 
|  | public static boolean verifyNotInternalSynthetic(ClassReference reference) { | 
|  | return verifyNotInternalSynthetic(reference.getDescriptor()); | 
|  | } | 
|  |  | 
|  | public static boolean verifyNotInternalSynthetic(String typeBinaryNameOrDescriptor) { | 
|  | assert !typeBinaryNameOrDescriptor.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Visible via package protection in SyntheticItemsTestUtils. | 
|  |  | 
|  | enum Phase { | 
|  | INTERNAL, | 
|  | EXTERNAL | 
|  | } | 
|  |  | 
|  | static String getPhaseSeparator(Phase phase) { | 
|  | assert phase != null; | 
|  | return phase == Phase.INTERNAL | 
|  | ? INTERNAL_SYNTHETIC_CLASS_SEPARATOR | 
|  | : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR; | 
|  | } | 
|  |  | 
|  | static ClassReference makeSyntheticReferenceForTest( | 
|  | ClassReference context, SyntheticKind kind, String id) { | 
|  | return Reference.classFromDescriptor( | 
|  | createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id)); | 
|  | } | 
|  |  | 
|  | static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) { | 
|  | String typeName = clazz.getTypeName(); | 
|  | if (kind.isFixedSuffixSynthetic()) { | 
|  | assert phase == null; | 
|  | return clazz.getBinaryName().endsWith(kind.descriptor); | 
|  | } | 
|  | String separator = getPhaseSeparator(phase); | 
|  | int i = typeName.lastIndexOf(separator); | 
|  | return i >= 0 && checkMatchFrom(kind, typeName, i, separator, phase == Phase.EXTERNAL); | 
|  | } | 
|  |  | 
|  | private static boolean checkMatchFrom( | 
|  | SyntheticKind kind, | 
|  | String name, | 
|  | int i, | 
|  | String externalSyntheticClassSeparator, | 
|  | boolean checkIntSuffix) { | 
|  | int end = i + externalSyntheticClassSeparator.length() + kind.descriptor.length(); | 
|  | if (end >= name.length()) { | 
|  | return false; | 
|  | } | 
|  | String prefix = name.substring(i, end); | 
|  | return prefix.equals(externalSyntheticClassSeparator + kind.descriptor) | 
|  | && (!checkIntSuffix || isInt(name.substring(end))); | 
|  | } | 
|  |  | 
|  | private static boolean isInt(String str) { | 
|  | if (str.isEmpty()) { | 
|  | return false; | 
|  | } | 
|  | if ('0' == str.charAt(0)) { | 
|  | return str.length() == 1; | 
|  | } | 
|  | for (int i = 0; i < str.length(); i++) { | 
|  | if (!Character.isDigit(str.charAt(i))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } |