Decouple Stack Manipulation logic in IndyStringDesugaring to LangModelHelper.
#java11 #desugar #refactor
- The abstracted logic is to be a shared API with Desugaring interactions with Android APIs.
PiperOrigin-RevId: 299264709
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
index 7cb47c7..75fa1c5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
@@ -16,9 +16,14 @@
package com.google.devtools.build.android.desugar.langmodel;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import org.objectweb.asm.Type;
/**
@@ -28,6 +33,59 @@
@AutoValue
public abstract class ClassName implements TypeMappable<ClassName> {
+ public static final String IN_PROCESS_LABEL = "__desugar__/";
+
+ private static final String TYPE_ADAPTER_PACKAGE_ROOT = "desugar/runtime/typeadapter/";
+
+ /**
+ * The primitive type as specified at
+ * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
+ */
+ private static final ImmutableMap<String, Type> PRIMITIVES_TYPES =
+ ImmutableMap.<String, Type>builder()
+ .put("V", Type.VOID_TYPE)
+ .put("Z", Type.BOOLEAN_TYPE)
+ .put("C", Type.CHAR_TYPE)
+ .put("B", Type.BYTE_TYPE)
+ .put("S", Type.SHORT_TYPE)
+ .put("I", Type.INT_TYPE)
+ .put("F", Type.FLOAT_TYPE)
+ .put("J", Type.LONG_TYPE)
+ .put("D", Type.DOUBLE_TYPE)
+ .build();
+
+ /**
+ * The primitive type as specified at
+ * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
+ */
+ private static final ImmutableMap<ClassName, ClassName> PRIMITIVES_TO_BOXED_TYPES =
+ ImmutableMap.<ClassName, ClassName>builder()
+ .put(ClassName.create(Type.VOID_TYPE), ClassName.create("java/lang/Void"))
+ .put(ClassName.create(Type.BOOLEAN_TYPE), ClassName.create("java/lang/Boolean"))
+ .put(ClassName.create(Type.CHAR_TYPE), ClassName.create("java/lang/Character"))
+ .put(ClassName.create(Type.BYTE_TYPE), ClassName.create("java/lang/Byte"))
+ .put(ClassName.create(Type.SHORT_TYPE), ClassName.create("java/lang/Short"))
+ .put(ClassName.create(Type.INT_TYPE), ClassName.create("java/lang/Integer"))
+ .put(ClassName.create(Type.FLOAT_TYPE), ClassName.create("java/lang/Float"))
+ .put(ClassName.create(Type.LONG_TYPE), ClassName.create("java/lang/Long"))
+ .put(ClassName.create(Type.DOUBLE_TYPE), ClassName.create("java/lang/Double"))
+ .build();
+
+ private static final ImmutableBiMap<String, String> DELIVERY_TYPE_MAPPINGS =
+ ImmutableBiMap.<String, String>builder()
+ .put("java/", "j$/")
+ .put("javadesugar/", "jd$/")
+ .build();
+
+ public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
+ new TypeMapper(ClassName::verbatimName);
+
+ public static final TypeMapper DELIVERY_TYPE_MAPPER =
+ new TypeMapper(ClassName::verbatimToDelivery);
+
+ public static final TypeMapper VERBATIM_TYPE_MAPPER =
+ new TypeMapper(ClassName::deliveryToVerbatim);
+
/**
* The textual binary name used to index the class name, as defined at,
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
@@ -35,7 +93,7 @@
public abstract String binaryName();
public static ClassName create(String binaryName) {
- checkState(
+ checkArgument(
!binaryName.contains("."),
"Expected a binary/internal class name ('/'-delimited) instead of a qualified name."
+ " Actual: (%s)",
@@ -52,7 +110,24 @@
}
public final Type toAsmObjectType() {
- return Type.getObjectType(binaryName());
+ return isPrimitive() ? PRIMITIVES_TYPES.get(binaryName()) : Type.getObjectType(binaryName());
+ }
+
+ public final ClassName toBoxedType() {
+ checkState(isPrimitive(), "Expected a primitive type for type boxing, but got %s", this);
+ return PRIMITIVES_TO_BOXED_TYPES.get(this);
+ }
+
+ public final boolean isPrimitive() {
+ return PRIMITIVES_TYPES.containsKey(binaryName());
+ }
+
+ public final boolean isWideType() {
+ return "D".equals(binaryName()) || "J".equals(binaryName());
+ }
+
+ public final boolean isBoxedType() {
+ return PRIMITIVES_TO_BOXED_TYPES.containsValue(this);
}
public final String qualifiedName() {
@@ -63,22 +138,126 @@
return ClassName.create(binaryName() + '$' + innerClassSimpleName);
}
+ public final String getPackageName() {
+ String binaryName = binaryName();
+ int i = binaryName.lastIndexOf('/');
+ return i < 0 ? "" : binaryName.substring(0, i + 1);
+ }
+
public final String simpleName() {
String binaryName = binaryName();
int i = binaryName.lastIndexOf('/');
return i < 0 ? binaryName : binaryName.substring(i + 1);
}
+ public final ClassName withSimpleNameSuffix(String suffix) {
+ return ClassName.create(binaryName() + suffix);
+ }
+
public final String classFilePathName() {
return binaryName() + ".class";
}
+ public final boolean hasInProcessLabel() {
+ return hasPackagePrefix(IN_PROCESS_LABEL);
+ }
+
+ private ClassName stripInProcessLabel() {
+ return stripPackagePrefix(IN_PROCESS_LABEL);
+ }
+
+ private ClassName stripInProcessLabelIfAny() {
+ return hasInProcessLabel() ? stripPackagePrefix(IN_PROCESS_LABEL) : this;
+ }
+
+ /** Strips out in-process labels if any. */
+ public final ClassName verbatimName() {
+ return stripInProcessLabelIfAny().deliveryToVerbatim();
+ }
+
+ public final ClassName typeAdapterOwner() {
+ return verbatimName().withSimpleNameSuffix("Adapter").prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+ }
+
+ public final ClassName typeConverterOwner() {
+ return verbatimName()
+ .withSimpleNameSuffix("Converter")
+ .prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
+ }
+
+ public final ClassName verbatimToDelivery() {
+ return DELIVERY_TYPE_MAPPINGS.keySet().stream()
+ .filter(this::hasPackagePrefix)
+ .map(prefix -> replacePackagePrefix(prefix, DELIVERY_TYPE_MAPPINGS.get(prefix)))
+ .findAny()
+ .orElse(this);
+ }
+
+ public final ClassName deliveryToVerbatim() {
+ ImmutableBiMap<String, String> verbatimTypeMappings = DELIVERY_TYPE_MAPPINGS.inverse();
+ return verbatimTypeMappings.keySet().stream()
+ .filter(this::hasPackagePrefix)
+ .map(prefix -> replacePackagePrefix(prefix, verbatimTypeMappings.get(prefix)))
+ .findAny()
+ .orElse(this);
+ }
+
public final ClassName prependPrefix(String prefix) {
+ checkPackagePrefixFormat(prefix);
return ClassName.create(prefix + binaryName());
}
+ public final boolean hasPackagePrefix(String prefix) {
+ return binaryName().startsWith(prefix);
+ }
+
+ public final boolean hasAnyPackagePrefix(String... prefixes) {
+ return Arrays.stream(prefixes).anyMatch(this::hasPackagePrefix);
+ }
+
+ public final boolean hasAnyPackagePrefix(Collection<String> prefixes) {
+ return prefixes.stream().anyMatch(this::hasPackagePrefix);
+ }
+
+ public final ClassName stripPackagePrefix(String prefix) {
+ return replacePackagePrefix(/* originalPrefix= */ prefix, /* targetPrefix= */ "");
+ }
+
+ public final ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
+ checkState(
+ hasPackagePrefix(originalPrefix),
+ "Expected %s to have a package prefix of (%s) before stripping.",
+ this,
+ originalPrefix);
+ checkPackagePrefixFormat(targetPrefix);
+ return ClassName.create(targetPrefix + binaryName().substring(originalPrefix.length()));
+ }
+
@Override
public ClassName acceptTypeMapper(TypeMapper typeMapper) {
return typeMapper.map(this);
}
+
+ private static void checkPackagePrefixFormat(String prefix) {
+ checkArgument(
+ prefix.isEmpty() || prefix.endsWith("/"),
+ "Expected (%s) to be a package prefix of ending with '/'.",
+ prefix);
+ checkArgument(
+ !prefix.contains("."),
+ "Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
+ prefix);
+ }
+
+ public boolean isInProcessCoreType() {
+ return hasInProcessLabel() && stripInProcessLabel().isVerbatimCoreType();
+ }
+
+ public boolean isVerbatimCoreType() {
+ return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.keySet());
+ }
+
+ public boolean isDeliveryCoreType() {
+ return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.values());
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/InvocationSiteTransformationRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/InvocationSiteTransformationRecord.java
new file mode 100644
index 0000000..b95ea0d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/InvocationSiteTransformationRecord.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.langmodel;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+
+/** A record that tracks the method invocation transformations. */
+@AutoValue
+public abstract class InvocationSiteTransformationRecord {
+
+ public abstract ImmutableSet<MethodInvocationSite> record();
+
+ public static InvocationSiteTransformationRecordBuilder builder() {
+ return new AutoValue_InvocationSiteTransformationRecord.Builder();
+ }
+
+ /** The builder for {@link InvocationSiteTransformationRecord}. */
+ @AutoValue.Builder
+ public abstract static class InvocationSiteTransformationRecordBuilder {
+
+ abstract ImmutableSet.Builder<MethodInvocationSite> recordBuilder();
+
+ public final InvocationSiteTransformationRecordBuilder addTransformation(
+ MethodInvocationSite originalMethodInvocationSite) {
+ recordBuilder().add(originalMethodInvocationSite);
+ return this;
+ }
+
+ public abstract InvocationSiteTransformationRecord build();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
index d339ed5..a67657b 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/LangModelHelper.java
@@ -16,10 +16,16 @@
package com.google.devtools.build.android.desugar.langmodel;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Streams;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -28,22 +34,6 @@
public final class LangModelHelper {
/**
- * The primitive type as specified at
- * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
- */
- private static final ImmutableMap<Type, Type> PRIMITIVES_TO_BOXED_TYPES =
- ImmutableMap.<Type, Type>builder()
- .put(Type.INT_TYPE, Type.getObjectType("java/lang/Integer"))
- .put(Type.BOOLEAN_TYPE, Type.getObjectType("java/lang/Boolean"))
- .put(Type.BYTE_TYPE, Type.getObjectType("java/lang/Byte"))
- .put(Type.CHAR_TYPE, Type.getObjectType("java/lang/Character"))
- .put(Type.SHORT_TYPE, Type.getObjectType("java/lang/Short"))
- .put(Type.DOUBLE_TYPE, Type.getObjectType("java/lang/Double"))
- .put(Type.FLOAT_TYPE, Type.getObjectType("java/lang/Float"))
- .put(Type.LONG_TYPE, Type.getObjectType("java/lang/Long"))
- .build();
-
- /**
* The lookup table for dup instructional opcodes. The row key is the count of words on stack top
* to be duplicated. The column key is gap in word count between the original word section on
* stack top and the post-duplicated word section underneath. See from
@@ -60,15 +50,6 @@
.put(2, 2, Opcodes.DUP2_X2)
.build();
- /** Whether the given type is a primitive type */
- public static boolean isPrimitive(Type type) {
- return PRIMITIVES_TO_BOXED_TYPES.containsKey(type);
- }
-
- public static Type toBoxedType(Type primitiveType) {
- return PRIMITIVES_TO_BOXED_TYPES.get(primitiveType);
- }
-
/**
* Returns the operation code for pop operations with a single instruction support by their type
* sizes on stack top
@@ -163,5 +144,224 @@
}
}
+ /**
+ * Emits bytecode to allocate a new object array, stores the bottom values in the operand stack to
+ * the new array, and replaces the bottom values with a reference to the newly-allocated array.
+ *
+ * <p>Operand Stack:
+ * <li>Before instructions: [Stack Top]..., value_n, value_n-1, ..., value_2, value_1, value_0
+ * <li>After instructions: [Stack Top] ..., value_n, arrayref
+ *
+ * <p>where n is the size of {@code expectedTypesOnOperandStack} and is expected to be equal
+ * to array length referenced by arrayref
+ *
+ * @param mv The current method visitor that is visiting the class.
+ * @param mappers Applies to an operand stack value if tested positive on {@code filter}.
+ * @param expectedTypesOnOperandStack The expected types at the bottom of the operand stack. The
+ * end of the list corresponds to the the bottom of the operand stack.
+ */
+ public static ImmutableList<ClassName> collapseStackValuesToObjectArray(
+ MethodVisitor mv,
+ ImmutableList<Function<ClassName, Optional<MethodInvocationSite>>> mappers,
+ ImmutableList<ClassName> expectedTypesOnOperandStack) {
+ // Creates an array of java/lang/Object to store the values on top of the operand stack that
+ // are subject to string concatenation.
+ int numOfValuesOnOperandStack = expectedTypesOnOperandStack.size();
+ visitPushInstr(mv, numOfValuesOnOperandStack);
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+
+ // To preserve the order of the operands to be string-concatenated, we slot the values on
+ // the top of the stack to the end of the array.
+ List<ClassName> actualTypesOnObjectArray = new ArrayList<>(expectedTypesOnOperandStack);
+ for (int i = numOfValuesOnOperandStack - 1; i >= 0; i--) {
+ ClassName operandTypeName = expectedTypesOnOperandStack.get(i);
+ Type operandType = operandTypeName.toAsmObjectType();
+ // Pre-duplicates the array reference for next loop iteration use.
+ // Post-operation stack bottom to top:
+ // ..., value_i-1, arrayref, value_i, arrayref.
+ mv.visitInsn(
+ getTypeSizeAlignedDupOpcode(
+ ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(operandType)));
+
+ // Pushes the array index and adjusts the order of the values on stack top in the order
+ // of <bottom/> arrayref, index, value <top/> before emitting an aastore instruction.
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.aastore
+ // Post-operation stack bottom to top:
+ // ..., value_i-1, arrayref, value_i, arrayref, i.
+ visitPushInstr(mv, i);
+ // Cross-duplicates the array reference and index.
+ // Post-operation stack bottom to top:
+ // ..., value_i-1, arrayref, arrayref, i, value_i, arrayref, i.
+ mv.visitInsn(
+ getTypeSizeAlignedDupOpcode(
+ ImmutableList.of(Type.getType(Object.class), Type.getType(int.class)),
+ ImmutableList.of(operandType)));
+
+ // Pops arrayref, index, leaving the stack top as value_i.
+ // Post-operation stack bottom to top:
+ // ..., value_i-1, arrayref, arrayref, i, value_i.
+ mv.visitInsn(
+ getTypeSizeAlignedPopOpcode(
+ ImmutableList.of(Type.getType(Object.class), Type.getType(int.class))));
+
+ int targetArrayIndex = i;
+ mappers.stream()
+ .map(mapper -> mapper.apply(actualTypesOnObjectArray.get(targetArrayIndex)))
+ .flatMap(Streams::stream)
+ .forEach(
+ typeConversionSite -> {
+ typeConversionSite.accept(mv);
+ actualTypesOnObjectArray.set(targetArrayIndex, typeConversionSite.returnTypeName());
+ });
+
+ // Post-operation stack bottom to top:
+ // ..., value_i-1, arrayref.
+ mv.visitInsn(Opcodes.AASTORE);
+ }
+ return ImmutableList.copyOf(actualTypesOnObjectArray);
+ }
+
+ /**
+ * Emits bytecode to replace the object array reference at the operand stack bottom with its array
+ * element values.
+ *
+ * <p>Operand Stack:
+ * <li>Before instructions: [Stack Top] ..., value_n, arrayref
+ * <li>After instructions: [Stack Top]..., value_n, value_n-1, ..., value_2, value_1, value_0
+ *
+ * <p>where n is the array length referenced by arrayref and is expected to be equal to the
+ * size of {@code expectedTypesOnOperandStack} expanded on the operand stack.
+ *
+ * @param mv The current method visitor that is visiting the class.
+ * @param expectedTypesOnOperandStack The expected types at the bottom of the operand stack. The
+ * end of the list corresponds to the the bottom of the operand stack.
+ */
+ public static void expandObjectArrayToStackValues(
+ MethodVisitor mv, ImmutableList<ClassName> expectedTypesOnOperandStack) {
+ int numOfValuesExpandedOnOperandStack = expectedTypesOnOperandStack.size();
+ for (int i = 0; i < numOfValuesExpandedOnOperandStack; i++) {
+ ClassName operandTypeName = expectedTypesOnOperandStack.get(i);
+ // Pre-duplicates the array reference for next loop iteration use.
+ // Post-operation stack bottom to top:
+ // ..., arrayref, arrayref
+ mv.visitInsn(Opcodes.DUP);
+
+ // Pushes the current array index on stack.
+ // Post-operation stack bottom to top:
+ // ..., arrayref, arrayref, i
+ visitPushInstr(mv, i);
+
+ // Post-operation stack bottom to top:
+ // ..., arrayref, obj_value_i
+ mv.visitInsn(Opcodes.AALOAD);
+
+ // Post-operation stack bottom to top:
+ // ..., arrayref, cast_and_unboxed_value_i
+ if (operandTypeName.isPrimitive()) {
+ ClassName boxedTypeName = operandTypeName.toBoxedType();
+ mv.visitTypeInsn(Opcodes.CHECKCAST, boxedTypeName.binaryName());
+ createBoxedTypeToPrimitiveInvocationSite(boxedTypeName).accept(mv);
+ } else if (!ClassName.create(Object.class).equals(operandTypeName)) {
+ mv.visitTypeInsn(Opcodes.CHECKCAST, operandTypeName.binaryName());
+ }
+
+ // ..., cast_and_unboxed_value_i, arrayref
+ if (operandTypeName.isWideType()) {
+ mv.visitInsn(Opcodes.DUP2_X1);
+ mv.visitInsn(Opcodes.POP2);
+ } else {
+ mv.visitInsn(Opcodes.SWAP);
+ }
+ }
+
+ // pops out the original arrayref.
+ mv.visitInsn(Opcodes.POP);
+ }
+
+ public static Optional<MethodInvocationSite> anyPrimitiveToStringInvocationSite(
+ ClassName className) {
+ return className.isPrimitive()
+ ? Optional.of(createPrimitiveToStringInvocationSite(className))
+ : Optional.empty();
+ }
+
+ /** Convenient factory method for converting a primitive type to string call site. */
+ private static MethodInvocationSite createPrimitiveToStringInvocationSite(
+ ClassName primitiveTypeName) {
+ checkArgument(
+ primitiveTypeName.isPrimitive(),
+ "Expected a primitive type for a type boxing call site, but got %s",
+ primitiveTypeName);
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ MethodKey.create(
+ primitiveTypeName.toBoxedType(),
+ "toString",
+ Type.getMethodDescriptor(
+ Type.getType(String.class), primitiveTypeName.toAsmObjectType())))
+ .setIsInterface(false)
+ .build();
+ }
+
+ /** Convenient factory method for converting a primitive type to string call site. */
+ public static Optional<MethodInvocationSite> anyPrimitiveToBoxedTypeInvocationSite(
+ ClassName className) {
+ return className.isPrimitive()
+ ? Optional.of(createPrimitiveToBoxedTypeInvocationSite(className))
+ : Optional.empty();
+ }
+
+ private static MethodInvocationSite createPrimitiveToBoxedTypeInvocationSite(
+ ClassName primitiveTypeName) {
+ checkArgument(
+ primitiveTypeName.isPrimitive(),
+ "Expected a primitive type for a type boxing call site, but got %s",
+ primitiveTypeName);
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(
+ MethodKey.create(
+ primitiveTypeName.toBoxedType(),
+ "valueOf",
+ Type.getMethodDescriptor(
+ primitiveTypeName.toBoxedType().toAsmObjectType(),
+ primitiveTypeName.toAsmObjectType())))
+ .setIsInterface(false)
+ .build();
+ }
+
+ private static MethodInvocationSite createBoxedTypeToPrimitiveInvocationSite(
+ ClassName boxedType) {
+ String boxedTypeBinaryName = boxedType.binaryName();
+ final MethodKey typeUnboxingMethod;
+ if ("java/lang/Boolean".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "booleanValue", "()Z");
+ } else if ("java/lang/Character".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "charValue", "()C");
+ } else if ("java/lang/Byte".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "byteValue", "()B");
+ } else if ("java/lang/Short".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "shortValue", "()S");
+ } else if ("java/lang/Integer".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "intValue", "()I");
+ } else if ("java/lang/Float".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "floatValue", "()F");
+ } else if ("java/lang/Long".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "longValue", "()J");
+ } else if ("java/lang/Double".contentEquals(boxedTypeBinaryName)) {
+ typeUnboxingMethod = MethodKey.create(boxedType, "doubleValue", "()D");
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "Expected a boxed type to create a type boxing call site, but got %s", boxedType));
+ }
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKEVIRTUAL)
+ .setMethod(typeUnboxingMethod)
+ .setIsInterface(false)
+ .build();
+ }
+
private LangModelHelper() {}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodDeclInfo.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodDeclInfo.java
index 942276c..7580fa9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodDeclInfo.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodDeclInfo.java
@@ -88,6 +88,10 @@
return methodKey().getReturnType();
}
+ public final ClassName returnTypeName() {
+ return methodKey().getReturnTypeName();
+ }
+
public final ImmutableList<Type> argumentTypes() {
return ImmutableList.copyOf(methodKey().getArgumentTypes());
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
new file mode 100644
index 0000000..dffbb3b
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodInvocationSite.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.langmodel;
+
+import com.google.auto.value.AutoValue;
+import org.objectweb.asm.MethodVisitor;
+
+/** A value object that represents an method invocation site. */
+@AutoValue
+public abstract class MethodInvocationSite implements TypeMappable<MethodInvocationSite> {
+
+ public abstract MemberUseKind invocationKind();
+
+ public abstract MethodKey method();
+
+ public abstract boolean isInterface();
+
+ public static MethodInvocationSiteBuilder builder() {
+ return new AutoValue_MethodInvocationSite.Builder();
+ }
+
+ /** Convenient factory method for use in the callback of {@link MethodVisitor#visitMethodInsn}. */
+ public static MethodInvocationSite create(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ return builder()
+ .setInvocationKind(MemberUseKind.fromValue(opcode))
+ .setMethod(MethodKey.create(ClassName.create(owner), name, descriptor))
+ .setIsInterface(isInterface)
+ .build();
+ }
+
+ public abstract MethodInvocationSiteBuilder toBuilder();
+
+ public final int invokeOpcode() {
+ return invocationKind().getOpcode();
+ }
+
+ public final ClassName owner() {
+ return method().owner();
+ }
+
+ public final String name() {
+ return method().name();
+ }
+
+ public final String descriptor() {
+ return method().descriptor();
+ }
+
+ public final ClassName returnTypeName() {
+ return method().getReturnTypeName();
+ }
+
+ public final boolean isStaticInvocation() {
+ return invocationKind() == MemberUseKind.INVOKESTATIC;
+ }
+
+ public final boolean isConstructorInvocation() {
+ return method().isConstructor();
+ }
+
+ public final MethodVisitor accept(MethodVisitor mv) {
+ mv.visitMethodInsn(invokeOpcode(), owner().binaryName(), name(), descriptor(), isInterface());
+ return mv;
+ }
+
+ @Override
+ public MethodInvocationSite acceptTypeMapper(TypeMapper typeMapper) {
+ return toBuilder().setMethod(method().acceptTypeMapper(typeMapper)).build();
+ }
+
+ public final MethodInvocationSite toAdapterInvocationSite() {
+ return MethodInvocationSite.builder()
+ .setInvocationKind(MemberUseKind.INVOKESTATIC)
+ .setMethod(method().toArgumentTypeAdapter(isStaticInvocation()))
+ .setIsInterface(false)
+ .build();
+ }
+
+ /** The builder for {@link MethodInvocationSite}. */
+ @AutoValue.Builder
+ public abstract static class MethodInvocationSiteBuilder {
+
+ public abstract MethodInvocationSiteBuilder setInvocationKind(MemberUseKind value);
+
+ public abstract MethodInvocationSiteBuilder setMethod(MethodKey value);
+
+ public abstract MethodInvocationSiteBuilder setIsInterface(boolean value);
+
+ public abstract MethodInvocationSite build();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
index ca868c5..0bd249c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
@@ -17,6 +17,7 @@
package com.google.devtools.build.android.desugar.langmodel;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
@@ -58,6 +59,23 @@
return ImmutableList.copyOf(getArgumentTypeArray());
}
+ /** The formal parameter type names of a method. */
+ public ImmutableList<ClassName> getArgumentTypeNames() {
+ return getArgumentTypes().stream().map(ClassName::create).collect(toImmutableList());
+ }
+
+ public MethodKey toArgumentTypeAdapter(boolean fromStaticOrigin) {
+ ClassName typeAdapterOwner = owner().typeAdapterOwner();
+ checkState(
+ !isConstructor(), "Argument type adapter for constructor is not supported: %s. ", this);
+
+ return MethodKey.create(
+ typeAdapterOwner,
+ name(),
+ fromStaticOrigin ? descriptor() : instanceMethodToStaticDescriptor())
+ .acceptTypeMapper(ClassName.DELIVERY_TYPE_MAPPER);
+ }
+
/** The synthetic constructor for a private constructor. */
public final MethodKey bridgeOfConstructor(ClassName nestCompanion) {
checkState(isConstructor(), "Expect to use for a constructor but is %s", this);
@@ -79,7 +97,7 @@
/** The synthetic bridge method for a private instance method in a class. */
public final MethodKey bridgeOfClassInstanceMethod() {
- return create(owner(), nameWithSuffix("bridge"), instanceMethodToStaticDescriptor(this));
+ return create(owner(), nameWithSuffix("bridge"), this.instanceMethodToStaticDescriptor());
}
/** The substitute method for a private static method in an interface. */
@@ -90,20 +108,16 @@
/** The substitute method for a private instance method in an interface. */
public final MethodKey substituteOfInterfaceInstanceMethod() {
- return create(owner(), name(), instanceMethodToStaticDescriptor(this));
+ return create(owner(), name(), this.instanceMethodToStaticDescriptor());
}
/** The descriptor of the static version of a given instance method. */
- private static String instanceMethodToStaticDescriptor(MethodKey methodKey) {
- checkState(!methodKey.isConstructor(), "Expect a Non-constructor method: %s", methodKey);
- ImmutableList<Type> argumentTypes = methodKey.getArgumentTypes();
+ private String instanceMethodToStaticDescriptor() {
+ checkState(!isConstructor(), "Expect a Non-constructor method: %s", this);
+ ImmutableList<Type> argumentTypes = getArgumentTypes();
ImmutableList<Type> bridgeMethodArgTypes =
- ImmutableList.<Type>builder()
- .add(methodKey.ownerAsmObjectType())
- .addAll(argumentTypes)
- .build();
- return Type.getMethodDescriptor(
- methodKey.getReturnType(), bridgeMethodArgTypes.toArray(new Type[0]));
+ ImmutableList.<Type>builder().add(ownerAsmObjectType()).addAll(argumentTypes).build();
+ return Type.getMethodDescriptor(getReturnType(), bridgeMethodArgTypes.toArray(new Type[0]));
}
@Override
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
index f86e7a9..21ca226 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java
@@ -16,8 +16,7 @@
package com.google.devtools.build.android.desugar.strconcat;
-import static com.google.devtools.build.android.desugar.langmodel.LangModelHelper.isPrimitive;
-import static com.google.devtools.build.android.desugar.langmodel.LangModelHelper.toBoxedType;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.devtools.build.android.desugar.langmodel.LangModelHelper.visitPushInstr;
import com.google.common.collect.ImmutableList;
@@ -27,6 +26,7 @@
import com.google.devtools.build.android.desugar.langmodel.LangModelHelper;
import com.google.devtools.build.android.desugar.langmodel.MemberUseKind;
import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import java.util.Arrays;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
@@ -105,62 +105,13 @@
// StringConcatFactory#makeConcatWithConstants
classMemberUseCounter.incrementMemberUseCount(bootstrapMethodInvocation);
- // Creates an array of java/lang/Object to store the values on top of the operand stack that
- // are subject to string concatenation.
- Type[] typesOfValuesOnOperandStack = Type.getArgumentTypes(descriptor);
- int numOfValuesOnOperandStack = typesOfValuesOnOperandStack.length;
- visitPushInstr(mv, numOfValuesOnOperandStack);
- visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+ LangModelHelper.collapseStackValuesToObjectArray(
+ this,
+ ImmutableList.of(LangModelHelper::anyPrimitiveToStringInvocationSite),
+ Arrays.stream(Type.getArgumentTypes(descriptor))
+ .map(ClassName::create)
+ .collect(toImmutableList()));
- // To preserve the order of the operands to be string-concatenated, we slot the values on
- // the top of the stack to the end of the array.
- for (int i = numOfValuesOnOperandStack - 1; i >= 0; i--) {
- Type operandType = typesOfValuesOnOperandStack[i];
- // Pre-duplicates the array reference for next loop iteration use.
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref, value_i, arrayref.
- visitInsn(
- LangModelHelper.getTypeSizeAlignedDupOpcode(
- ImmutableList.of(Type.getType(Object.class)), ImmutableList.of(operandType)));
-
- // Pushes the array index and adjusts the order of the values on stack top in the order
- // of <bottom/> arrayref, index, value <top/> before emitting an aastore instruction.
- // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.aastore
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref, value_i, arrayref, i.
- visitPushInstr(mv, i);
- // Cross-duplicates the array reference and index.
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref, arrayref, i, value_i, arrayref, i.
- visitInsn(
- LangModelHelper.getTypeSizeAlignedDupOpcode(
- ImmutableList.of(Type.getType(Object.class), Type.getType(int.class)),
- ImmutableList.of(operandType)));
-
- // Pops arrayref, index, leaving the stack top as value_i.
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref, arrayref, i, value_i.
- visitInsn(
- LangModelHelper.getTypeSizeAlignedPopOpcode(
- ImmutableList.of(Type.getType(Object.class), Type.getType(int.class))));
-
- if (isPrimitive(operandType)) {
- // Explicitly computes the string value of primitive types, so that they can be stored
- // in the Object[] array.
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref, arrayref, i, processed_value_i.
- Type boxedType = toBoxedType(operandType);
- visitMethodInsn(
- Opcodes.INVOKESTATIC,
- boxedType.getInternalName(),
- "toString",
- Type.getMethodDescriptor(Type.getType(String.class), operandType),
- /* isInterface= */ false);
- }
- // Post-operation stack bottom to top:
- // ..., value_i-1, arrayref.
- visitInsn(Opcodes.AASTORE);
- }
String recipe = (String) bootstrapMethodArguments[0];
visitLdcInsn(recipe);
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java
index e7005c7..f4abb75 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java
@@ -163,10 +163,20 @@
}
/**
+ * Add JVM-flag-specified Java source files subject to be compiled during test execution. It is
+ * expected the value associated with `jvmFlagKey` to be a space-separated Strings. E.g. on the
+ * command line you would set it like: -Dinput_srcs="path1 path2 path3", and use <code>
+ * .addSourceInputsFromJvmFlag("input_srcs").</code> in your test class.
+ */
+ public DesugarRuleBuilder addJarInputsFromJvmFlag(String jvmFlagKey) {
+ return addInputs(getRuntimePathsFromJvmFlag(jvmFlagKey));
+ }
+
+ /**
* A helper method that reads file paths into an array from the JVM flag value associated with
* {@param jvmFlagKey}.
*/
- private static Path[] getRuntimePathsFromJvmFlag(String jvmFlagKey) {
+ public static Path[] getRuntimePathsFromJvmFlag(String jvmFlagKey) {
return Splitter.on(" ").trimResults().splitToList(System.getProperty(jvmFlagKey)).stream()
.map(Paths::get)
.toArray(Path[]::new);