blob: f86e7a92d57c38db26ee8504546e41a8da14ef12 [file] [log] [blame]
/*
* Copyright 2019 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.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.devtools.build.android.desugar.langmodel.LangModelHelper.visitPushInstr;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.android.desugar.langmodel.ClassMemberUse;
import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter;
import com.google.devtools.build.android.desugar.langmodel.ClassName;
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 org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/** Desugars indy string concatenations by replacement with string builders. */
public final class IndyStringConcatDesugaring extends ClassVisitor {
public static final ClassMemberUse INVOKE_JDK11_STRING_CONCAT =
ClassMemberUse.create(
MethodKey.create(
ClassName.create("java/lang/invoke/StringConcatFactory"),
"makeConcatWithConstants",
"(Ljava/lang/invoke/MethodHandles$Lookup;"
+ "Ljava/lang/String;"
+ "Ljava/lang/invoke/MethodType;"
+ "Ljava/lang/String;"
+ "[Ljava/lang/Object;)"
+ "Ljava/lang/invoke/CallSite;"),
MemberUseKind.INVOKEDYNAMIC);
private static final ClassMemberUse INVOKE_STRING_CONCAT_REPLACEMENT_METHOD =
ClassMemberUse.create(
MethodKey.create(
ClassName.create("com/google/devtools/build/android/desugar/runtime/StringConcats"),
"concat",
"([Ljava/lang/Object;"
+ "Ljava/lang/String;"
+ "[Ljava/lang/Object;)"
+ "Ljava/lang/String;"),
MemberUseKind.INVOKESTATIC);
private final ClassMemberUseCounter classMemberUseCounter;
public IndyStringConcatDesugaring(
ClassMemberUseCounter classMemberUseCounter, ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor);
this.classMemberUseCounter = classMemberUseCounter;
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return mv == null
? null
: new IndifiedStringConcatInvocationConverter(classMemberUseCounter, api, mv);
}
static final class IndifiedStringConcatInvocationConverter extends MethodVisitor {
private final ClassMemberUseCounter classMemberUseCounter;
IndifiedStringConcatInvocationConverter(
ClassMemberUseCounter classMemberUseCounter, int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
this.classMemberUseCounter = classMemberUseCounter;
}
@Override
public void visitInvokeDynamicInsn(
String name,
String descriptor,
Handle bootstrapMethodHandle,
Object... bootstrapMethodArguments) {
ClassMemberUse bootstrapMethodInvocation =
ClassMemberUse.create(
MethodKey.create(
ClassName.create(bootstrapMethodHandle.getOwner()),
bootstrapMethodHandle.getName(),
bootstrapMethodHandle.getDesc()),
MemberUseKind.INVOKEDYNAMIC);
if (INVOKE_JDK11_STRING_CONCAT.equals(bootstrapMethodInvocation)) {
// Increment the counter for the bootstrap method invocation of
// 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");
// 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);
// Stores the constants into an array.
visitPushInstr(mv, bootstrapMethodArguments.length - 1);
visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
for (int i = 1; i < bootstrapMethodArguments.length; i++) {
visitPushInstr(mv, i - 1);
visitLdcInsn(bootstrapMethodArguments[i]);
visitInsn(Opcodes.AASTORE);
}
INVOKE_STRING_CONCAT_REPLACEMENT_METHOD.acceptClassMethodInsn(this);
return;
}
super.visitInvokeDynamicInsn(
name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
}
}