Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 1 | // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | package stringconcat; |
| 5 | |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 6 | import java.io.IOException; |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 7 | import java.io.InputStream; |
| 8 | import java.io.OutputStream; |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 9 | import java.lang.invoke.CallSite; |
| 10 | import java.lang.invoke.MethodHandles; |
| 11 | import java.lang.invoke.MethodType; |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 12 | import java.nio.file.Files; |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 13 | import java.nio.file.Path; |
| 14 | import java.nio.file.Paths; |
| 15 | import java.util.ArrayList; |
| 16 | import java.util.List; |
| 17 | import org.objectweb.asm.ClassReader; |
| 18 | import org.objectweb.asm.ClassVisitor; |
| 19 | import org.objectweb.asm.ClassWriter; |
| 20 | import org.objectweb.asm.Handle; |
| 21 | import org.objectweb.asm.Label; |
| 22 | import org.objectweb.asm.MethodVisitor; |
| 23 | import org.objectweb.asm.Opcodes; |
| 24 | |
| 25 | public class TestGenerator { |
| 26 | private static final String RECIPE_PREFIX = "RECIPE:"; |
| 27 | |
| 28 | private static final String STRING_CONCAT_FACTORY = "java/lang/invoke/StringConcatFactory"; |
| 29 | |
| 30 | private static final Handle MAKE_CONCAT_WITH_CONSTANTS = new Handle( |
| 31 | Opcodes.H_INVOKESTATIC, STRING_CONCAT_FACTORY, "makeConcatWithConstants", |
| 32 | MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, |
| 33 | MethodType.class, String.class, Object[].class).toMethodDescriptorString(), |
| 34 | false); |
| 35 | |
| 36 | private static final Handle MAKE_CONCAT = new Handle( |
| 37 | Opcodes.H_INVOKESTATIC, STRING_CONCAT_FACTORY, "makeConcat", |
| 38 | MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, |
| 39 | String.class, MethodType.class).toMethodDescriptorString(), |
| 40 | false); |
| 41 | |
| 42 | public static void main(String[] args) throws IOException { |
| 43 | assert args.length == 1; |
| 44 | generateTests(Paths.get(args[0], |
| 45 | TestGenerator.class.getPackage().getName(), |
| 46 | StringConcat.class.getSimpleName() + ".class")); |
| 47 | } |
| 48 | |
| 49 | private static void generateTests(Path classNamePath) throws IOException { |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 50 | try (InputStream input = Files.newInputStream(classNamePath)) { |
| 51 | ClassReader cr = new ClassReader(input); |
| 52 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); |
| 53 | cr.accept( |
clementbera | 6c91c65 | 2019-04-15 17:28:42 +0200 | [diff] [blame] | 54 | new ClassVisitor(Opcodes.ASM7, cw) { |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 55 | @Override |
| 56 | public MethodVisitor visitMethod(int access, |
| 57 | final String methodName, String desc, String signature, String[] exceptions) { |
| 58 | MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions); |
clementbera | 6c91c65 | 2019-04-15 17:28:42 +0200 | [diff] [blame] | 59 | return new MethodVisitor(Opcodes.ASM7, mv) { |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 60 | private List<Object> recentConstants = new ArrayList<>(); |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 61 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 62 | @Override |
| 63 | public void visitLdcInsn(Object cst) { |
| 64 | if (!recentConstants.isEmpty() || |
| 65 | (cst instanceof String && ((String) cst).startsWith(RECIPE_PREFIX))) { |
| 66 | // Add the constant, don't push anything on stack. |
| 67 | recentConstants.add(cst); |
| 68 | return; |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 69 | } |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 70 | super.visitLdcInsn(cst); |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 71 | } |
| 72 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 73 | @Override |
| 74 | public void visitMethodInsn( |
| 75 | int opcode, String owner, String name, String desc, boolean itf) { |
| 76 | // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. |
| 77 | if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcat")) { |
| 78 | mv.visitInvokeDynamicInsn(MAKE_CONCAT.getName(), desc, MAKE_CONCAT); |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 79 | recentConstants.clear(); |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 80 | return; |
| 81 | } |
| 82 | |
| 83 | // Replace calls to 'makeConcat(...)' with appropriate `invokedynamic`. |
| 84 | if (opcode == Opcodes.INVOKESTATIC && name.equals("makeConcatWithConstants")) { |
| 85 | if (recentConstants.isEmpty()) { |
| 86 | throw new AssertionError("No constants detected in `" + |
| 87 | methodName + "`: call to " + name + desc); |
| 88 | } |
| 89 | recentConstants.set(0, |
| 90 | ((String) recentConstants.get(0)).substring(RECIPE_PREFIX.length())); |
| 91 | |
Jinseong Jeon | 66800e4 | 2019-03-20 11:57:24 -0700 | [diff] [blame] | 92 | mv.visitInvokeDynamicInsn( |
| 93 | MAKE_CONCAT_WITH_CONSTANTS.getName(), |
| 94 | removeLastParams(desc, recentConstants.size()), |
| 95 | MAKE_CONCAT_WITH_CONSTANTS, |
| 96 | recentConstants.toArray()); |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 97 | recentConstants.clear(); |
| 98 | return; |
| 99 | } |
| 100 | |
| 101 | // Otherwise fall back to default implementation. |
| 102 | super.visitMethodInsn(opcode, owner, name, desc, itf); |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 103 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 104 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 105 | private String removeLastParams(String descr, int paramsToRemove) { |
| 106 | MethodType methodType = |
| 107 | MethodType.fromMethodDescriptorString( |
| 108 | descr, this.getClass().getClassLoader()); |
| 109 | return methodType |
| 110 | .dropParameterTypes( |
| 111 | methodType.parameterCount() - paramsToRemove, |
| 112 | methodType.parameterCount()) |
| 113 | .toMethodDescriptorString(); |
| 114 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 115 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 116 | @Override |
| 117 | public void visitInsn(int opcode) { |
| 118 | switch (opcode) { |
| 119 | case Opcodes.ICONST_0: |
| 120 | if (!recentConstants.isEmpty()) { |
| 121 | recentConstants.add(0); |
| 122 | return; |
| 123 | } |
| 124 | break; |
| 125 | case Opcodes.ICONST_1: |
| 126 | if (!recentConstants.isEmpty()) { |
| 127 | recentConstants.add(1); |
| 128 | return; |
| 129 | } |
| 130 | break; |
| 131 | case Opcodes.ICONST_2: |
| 132 | if (!recentConstants.isEmpty()) { |
| 133 | recentConstants.add(2); |
| 134 | return; |
| 135 | } |
| 136 | break; |
| 137 | case Opcodes.ICONST_3: |
| 138 | if (!recentConstants.isEmpty()) { |
| 139 | recentConstants.add(3); |
| 140 | return; |
| 141 | } |
| 142 | break; |
| 143 | case Opcodes.ICONST_4: |
| 144 | if (!recentConstants.isEmpty()) { |
| 145 | recentConstants.add(4); |
| 146 | return; |
| 147 | } |
| 148 | break; |
| 149 | case Opcodes.ICONST_5: |
| 150 | if (!recentConstants.isEmpty()) { |
| 151 | recentConstants.add(5); |
| 152 | return; |
| 153 | } |
| 154 | break; |
| 155 | case Opcodes.ICONST_M1: |
| 156 | if (!recentConstants.isEmpty()) { |
| 157 | recentConstants.add(-1); |
| 158 | return; |
| 159 | } |
| 160 | break; |
| 161 | default: |
| 162 | recentConstants.clear(); |
| 163 | break; |
| 164 | } |
| 165 | super.visitInsn(opcode); |
| 166 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 167 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 168 | @Override |
| 169 | public void visitIntInsn(int opcode, int operand) { |
| 170 | recentConstants.clear(); |
| 171 | super.visitIntInsn(opcode, operand); |
| 172 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 173 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 174 | @Override |
| 175 | public void visitVarInsn(int opcode, int var) { |
| 176 | recentConstants.clear(); |
| 177 | super.visitVarInsn(opcode, var); |
| 178 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 179 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 180 | @Override |
| 181 | public void visitTypeInsn(int opcode, String type) { |
| 182 | recentConstants.clear(); |
| 183 | super.visitTypeInsn(opcode, type); |
| 184 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 185 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 186 | @Override |
| 187 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| 188 | recentConstants.clear(); |
| 189 | super.visitFieldInsn(opcode, owner, name, desc); |
| 190 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 191 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 192 | @Override |
| 193 | public void visitJumpInsn(int opcode, Label label) { |
| 194 | recentConstants.clear(); |
| 195 | super.visitJumpInsn(opcode, label); |
| 196 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 197 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 198 | @Override |
| 199 | public void visitIincInsn(int var, int increment) { |
| 200 | recentConstants.clear(); |
| 201 | super.visitIincInsn(var, increment); |
| 202 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 203 | |
Rico Wind | c0016d1 | 2017-10-26 13:39:32 +0200 | [diff] [blame] | 204 | @Override |
| 205 | public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { |
| 206 | recentConstants.clear(); |
| 207 | super.visitTableSwitchInsn(min, max, dflt, labels); |
| 208 | } |
| 209 | |
| 210 | @Override |
| 211 | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { |
| 212 | recentConstants.clear(); |
| 213 | super.visitLookupSwitchInsn(dflt, keys, labels); |
| 214 | } |
| 215 | |
| 216 | @Override |
| 217 | public void visitMultiANewArrayInsn(String desc, int dims) { |
| 218 | recentConstants.clear(); |
| 219 | super.visitMultiANewArrayInsn(desc, dims); |
| 220 | } |
| 221 | }; |
| 222 | } |
| 223 | }, 0); |
| 224 | try (OutputStream output = Files.newOutputStream(classNamePath)) { |
| 225 | output.write(cw.toByteArray()); |
| 226 | } |
| 227 | } |
Denis Vnukov | e443431 | 2017-10-12 12:45:50 -0700 | [diff] [blame] | 228 | } |
| 229 | } |