| // 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 stringconcat; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.invoke.CallSite; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.Handle; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| public class TestGenerator { |
| private static final String RECIPE_PREFIX = "RECIPE:"; |
| |
| private static final String STRING_CONCAT_FACTORY = "java/lang/invoke/StringConcatFactory"; |
| |
| private static final Handle MAKE_CONCAT_WITH_CONSTANTS = new Handle( |
| Opcodes.H_INVOKESTATIC, STRING_CONCAT_FACTORY, "makeConcatWithConstants", |
| MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, |
| MethodType.class, String.class, Object[].class).toMethodDescriptorString(), |
| false); |
| |
| private static final Handle MAKE_CONCAT = new Handle( |
| Opcodes.H_INVOKESTATIC, STRING_CONCAT_FACTORY, "makeConcat", |
| MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, |
| String.class, MethodType.class).toMethodDescriptorString(), |
| false); |
| |
| public static void main(String[] args) throws IOException { |
| assert args.length == 1; |
| generateTests(Paths.get(args[0], |
| TestGenerator.class.getPackage().getName(), |
| StringConcat.class.getSimpleName() + ".class")); |
| } |
| |
| private static void generateTests(Path classNamePath) throws IOException { |
| try (InputStream input = Files.newInputStream(classNamePath)) { |
| ClassReader cr = new ClassReader(input); |
| ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); |
| cr.accept( |
| new ClassVisitor(Opcodes.ASM6, cw) { |
| @Override |
| public MethodVisitor visitMethod(int access, |
| final String methodName, String desc, String signature, String[] exceptions) { |
| MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); |
| return new MethodVisitor(Opcodes.ASM6, mv) { |
| private List<Object> recentConstants = new ArrayList<>(); |
| |
| @Override |
| public void visitLdcInsn(Object cst) { |
| if (!recentConstants.isEmpty() || |
| (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) { |
| // Add the constant, don't push anything on stack. |
| recentConstants.add(cst); |
| return; |
| } |
| super.visitLdcInsn(cst); |
| } |
| |
| @Override |
| public void visitMethodInsn( |
| int opcode, String owner, String name, String desc, boolean itf) { |
| // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. |
| if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) { |
| mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT); |
| recentConstants.clear(); |
| return; |
| } |
| |
| // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. |
| if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) { |
| if (recentConstants.isEmpty()) { |
| throw new AssertionError("No constants detected in `" + |
| methodName + "`: call to " + name + desc); |
| } |
| recentConstants.set(0, |
| ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length())); |
| |
| mv.visitInvokeDynamicInsn(MAKE_CONCAT_WITH_CONSTANTS.getName(), |
| removeLastParams(desc, recentConstants.size()), MAKE_CONCAT_WITH_CONSTANTS, |
| recentConstants.toArray(new Object[recentConstants.size()])); |
| recentConstants.clear(); |
| return; |
| } |
| |
| // Otherwise fall back to default implementation. |
| super.visitMethodInsn(opcode, owner, name, desc, itf); |
| } |
| |
| private String removeLastParams(String descr, int paramsToRemove) { |
| MethodType methodType = |
| MethodType.fromMethodDescriptorString( |
| descr, this.getClass().getClassLoader()); |
| return methodType |
| .dropParameterTypes( |
| methodType.parameterCount() - paramsToRemove, |
| methodType.parameterCount()) |
| .toMethodDescriptorString(); |
| } |
| |
| @Override |
| public void visitInsn(int opcode) { |
| switch (opcode) { |
| case Opcodes.ICONST_0: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(0); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_1: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(1); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_2: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(2); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_3: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(3); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_4: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(4); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_5: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(5); |
| return; |
| } |
| break; |
| case Opcodes.ICONST_M1: |
| if (!recentConstants.isEmpty()) { |
| recentConstants.add(-1); |
| return; |
| } |
| break; |
| default: |
| recentConstants.clear(); |
| break; |
| } |
| super.visitInsn(opcode); |
| } |
| |
| @Override |
| public void visitIntInsn(int opcode, int operand) { |
| recentConstants.clear(); |
| super.visitIntInsn(opcode, operand); |
| } |
| |
| @Override |
| public void visitVarInsn(int opcode, int var) { |
| recentConstants.clear(); |
| super.visitVarInsn(opcode, var); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| recentConstants.clear(); |
| super.visitTypeInsn(opcode, type); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| recentConstants.clear(); |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitJumpInsn(int opcode, Label label) { |
| recentConstants.clear(); |
| super.visitJumpInsn(opcode, label); |
| } |
| |
| @Override |
| public void visitIincInsn(int var, int increment) { |
| recentConstants.clear(); |
| super.visitIincInsn(var, increment); |
| } |
| |
| @Override |
| public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { |
| recentConstants.clear(); |
| super.visitTableSwitchInsn(min, max, dflt, labels); |
| } |
| |
| @Override |
| public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { |
| recentConstants.clear(); |
| super.visitLookupSwitchInsn(dflt, keys, labels); |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| recentConstants.clear(); |
| super.visitMultiANewArrayInsn(desc, dims); |
| } |
| }; |
| } |
| }, 0); |
| try (OutputStream output = Files.newOutputStream(classNamePath)) { |
| output.write(cw.toByteArray()); |
| } |
| } |
| } |
| } |