| // 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.ASM7, 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.ASM7, 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()); | |
| 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()); | |
| } | |
| } | |
| } | |
| } |