001// ASM: a very small and fast Java bytecode manipulation framework 002// Copyright (c) 2000-2011 INRIA, France Telecom 003// All rights reserved. 004// 005// Redistribution and use in source and binary forms, with or without 006// modification, are permitted provided that the following conditions 007// are met: 008// 1. Redistributions of source code must retain the above copyright 009// notice, this list of conditions and the following disclaimer. 010// 2. Redistributions in binary form must reproduce the above copyright 011// notice, this list of conditions and the following disclaimer in the 012// documentation and/or other materials provided with the distribution. 013// 3. Neither the name of the copyright holders nor the names of its 014// contributors may be used to endorse or promote products derived from 015// this software without specific prior written permission. 016// 017// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 018// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 019// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 020// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 021// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 022// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 023// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 024// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 025// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 026// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 027// THE POSSIBILITY OF SUCH DAMAGE. 028package io.ebean.enhance.asm.commons; 029 030import io.ebean.enhance.asm.*; 031 032/** 033 * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter 034 * allows one to easily add new local variables to a method. It may be used by inheriting from this 035 * class, but the preferred way of using it is via delegation: the next visitor in the chain can 036 * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a 037 * reference back to this {@link LocalVariablesSorter}). 038 * 039 * @author Chris Nokleberg 040 * @author Eugene Kuleshov 041 * @author Eric Bruneton 042 */ 043public class LocalVariablesSorter extends MethodVisitor { 044 045 /** The type of the java.lang.Object class. */ 046 private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); 047 048 /** 049 * The mapping from old to new local variable indices. A local variable at index i of size 1 is 050 * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to 051 * 'mapping[2*i+1]'. 052 */ 053 private int[] remappedVariableIndices = new int[40]; 054 055 /** 056 * The local variable types after remapping. The format of this array is the same as in {@link 057 * MethodVisitor#visitFrame}, except that long and double types use two slots. 058 */ 059 private Object[] remappedLocalTypes = new Object[20]; 060 061 /** The index of the first local variable, after formal parameters. */ 062 protected final int firstLocal; 063 064 /** The index of the next local variable to be created by {@link #newLocal}. */ 065 protected int nextLocal; 066 067 /** 068 * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>. 069 * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)} 070 * version. 071 * 072 * @param access access flags of the adapted method. 073 * @param descriptor the method's descriptor (see {@link Type}). 074 * @param methodVisitor the method visitor to which this adapter delegates calls. 075 * @throws IllegalStateException if a subclass calls this constructor. 076 */ 077 public LocalVariablesSorter( 078 final int access, final String descriptor, final MethodVisitor methodVisitor) { 079 this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor); 080 if (getClass() != LocalVariablesSorter.class) { 081 throw new IllegalStateException(); 082 } 083 } 084 085 /** 086 * Constructs a new {@link LocalVariablesSorter}. 087 * 088 * @param api the ASM API version implemented by this visitor. Must be one of the {@code 089 * ASM}<i>x</i> values in {@link Opcodes}. 090 * @param access access flags of the adapted method. 091 * @param descriptor the method's descriptor (see {@link Type}). 092 * @param methodVisitor the method visitor to which this adapter delegates calls. 093 */ 094 protected LocalVariablesSorter( 095 final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) { 096 super(api, methodVisitor); 097 nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0; 098 for (Type argumentType : Type.getArgumentTypes(descriptor)) { 099 nextLocal += argumentType.getSize(); 100 } 101 firstLocal = nextLocal; 102 } 103 104 @Override 105 public void visitVarInsn(final int opcode, final int varIndex) { 106 Type varType; 107 switch (opcode) { 108 case Opcodes.LLOAD: 109 case Opcodes.LSTORE: 110 varType = Type.LONG_TYPE; 111 break; 112 case Opcodes.DLOAD: 113 case Opcodes.DSTORE: 114 varType = Type.DOUBLE_TYPE; 115 break; 116 case Opcodes.FLOAD: 117 case Opcodes.FSTORE: 118 varType = Type.FLOAT_TYPE; 119 break; 120 case Opcodes.ILOAD: 121 case Opcodes.ISTORE: 122 varType = Type.INT_TYPE; 123 break; 124 case Opcodes.ALOAD: 125 case Opcodes.ASTORE: 126 case Opcodes.RET: 127 varType = OBJECT_TYPE; 128 break; 129 default: 130 throw new IllegalArgumentException("Invalid opcode " + opcode); 131 } 132 super.visitVarInsn(opcode, remap(varIndex, varType)); 133 } 134 135 @Override 136 public void visitIincInsn(final int varIndex, final int increment) { 137 super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment); 138 } 139 140 @Override 141 public void visitMaxs(final int maxStack, final int maxLocals) { 142 super.visitMaxs(maxStack, nextLocal); 143 } 144 145 @Override 146 public void visitLocalVariable( 147 final String name, 148 final String descriptor, 149 final String signature, 150 final Label start, 151 final Label end, 152 final int index) { 153 int remappedIndex = remap(index, Type.getType(descriptor)); 154 super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex); 155 } 156 157 @Override 158 public AnnotationVisitor visitLocalVariableAnnotation( 159 final int typeRef, 160 final TypePath typePath, 161 final Label[] start, 162 final Label[] end, 163 final int[] index, 164 final String descriptor, 165 final boolean visible) { 166 Type type = Type.getType(descriptor); 167 int[] remappedIndex = new int[index.length]; 168 for (int i = 0; i < remappedIndex.length; ++i) { 169 remappedIndex[i] = remap(index[i], type); 170 } 171 return super.visitLocalVariableAnnotation( 172 typeRef, typePath, start, end, remappedIndex, descriptor, visible); 173 } 174 175 @Override 176 public void visitFrame( 177 final int type, 178 final int numLocal, 179 final Object[] local, 180 final int numStack, 181 final Object[] stack) { 182 if (type != Opcodes.F_NEW) { // Uncompressed frame. 183 throw new IllegalArgumentException( 184 "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)"); 185 } 186 187 // Create a copy of remappedLocals. 188 Object[] oldRemappedLocals = new Object[remappedLocalTypes.length]; 189 System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length); 190 191 updateNewLocals(remappedLocalTypes); 192 193 // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the 194 // variables added with 'newLocal'. 195 int oldVar = 0; // Old local variable index. 196 for (int i = 0; i < numLocal; ++i) { 197 Object localType = local[i]; 198 if (localType != Opcodes.TOP) { 199 Type varType = OBJECT_TYPE; 200 if (localType == Opcodes.INTEGER) { 201 varType = Type.INT_TYPE; 202 } else if (localType == Opcodes.FLOAT) { 203 varType = Type.FLOAT_TYPE; 204 } else if (localType == Opcodes.LONG) { 205 varType = Type.LONG_TYPE; 206 } else if (localType == Opcodes.DOUBLE) { 207 varType = Type.DOUBLE_TYPE; 208 } else if (localType instanceof String) { 209 varType = Type.getObjectType((String) localType); 210 } 211 setFrameLocal(remap(oldVar, varType), localType); 212 } 213 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 214 } 215 216 // Remove TOP after long and double types as well as trailing TOPs. 217 oldVar = 0; 218 int newVar = 0; 219 int remappedNumLocal = 0; 220 while (oldVar < remappedLocalTypes.length) { 221 Object localType = remappedLocalTypes[oldVar]; 222 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 223 if (localType != null && localType != Opcodes.TOP) { 224 remappedLocalTypes[newVar++] = localType; 225 remappedNumLocal = newVar; 226 } else { 227 remappedLocalTypes[newVar++] = Opcodes.TOP; 228 } 229 } 230 231 // Visit the remapped frame. 232 super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack); 233 234 // Restore the original value of 'remappedLocals'. 235 remappedLocalTypes = oldRemappedLocals; 236 } 237 238 // ----------------------------------------------------------------------------------------------- 239 240 /** 241 * Constructs a new local variable of the given type. 242 * 243 * @param type the type of the local variable to be created. 244 * @return the identifier of the newly created local variable. 245 */ 246 public int newLocal(final Type type) { 247 Object localType; 248 switch (type.getSort()) { 249 case Type.BOOLEAN: 250 case Type.CHAR: 251 case Type.BYTE: 252 case Type.SHORT: 253 case Type.INT: 254 localType = Opcodes.INTEGER; 255 break; 256 case Type.FLOAT: 257 localType = Opcodes.FLOAT; 258 break; 259 case Type.LONG: 260 localType = Opcodes.LONG; 261 break; 262 case Type.DOUBLE: 263 localType = Opcodes.DOUBLE; 264 break; 265 case Type.ARRAY: 266 localType = type.getDescriptor(); 267 break; 268 case Type.OBJECT: 269 localType = type.getInternalName(); 270 break; 271 default: 272 throw new AssertionError(); 273 } 274 int local = newLocalMapping(type); 275 setLocalType(local, type); 276 setFrameLocal(local, localType); 277 return local; 278 } 279 280 /** 281 * Notifies subclasses that a new stack map frame is being visited. The array argument contains 282 * the stack map frame types corresponding to the local variables added with {@link #newLocal}. 283 * This method can update these types in place for the stack map frame being visited. The default 284 * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal} 285 * will have the same type in all stack map frames. But this behavior is not always the desired 286 * one, for instance if a local variable is added in the middle of a try/catch block: the frame 287 * for the exception handler should have a TOP type for this new local. 288 * 289 * @param newLocals the stack map frame types corresponding to the local variables added with 290 * {@link #newLocal} (and null for the others). The format of this array is the same as in 291 * {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The 292 * types for the current stack map frame must be updated in place in this array. 293 */ 294 protected void updateNewLocals(final Object[] newLocals) { 295 // The default implementation does nothing. 296 } 297 298 /** 299 * Notifies subclasses that a local variable has been added or remapped. The default 300 * implementation of this method does nothing. 301 * 302 * @param local a local variable identifier, as returned by {@link #newLocal}. 303 * @param type the type of the value being stored in the local variable. 304 */ 305 protected void setLocalType(final int local, final Type type) { 306 // The default implementation does nothing. 307 } 308 309 private void setFrameLocal(final int local, final Object type) { 310 int numLocals = remappedLocalTypes.length; 311 if (local >= numLocals) { 312 Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)]; 313 System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals); 314 remappedLocalTypes = newRemappedLocalTypes; 315 } 316 remappedLocalTypes[local] = type; 317 } 318 319 private int remap(final int varIndex, final Type type) { 320 if (varIndex + type.getSize() <= firstLocal) { 321 return varIndex; 322 } 323 int key = 2 * varIndex + type.getSize() - 1; 324 int size = remappedVariableIndices.length; 325 if (key >= size) { 326 int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)]; 327 System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size); 328 remappedVariableIndices = newRemappedVariableIndices; 329 } 330 int value = remappedVariableIndices[key]; 331 if (value == 0) { 332 value = newLocalMapping(type); 333 setLocalType(value, type); 334 remappedVariableIndices[key] = value + 1; 335 } else { 336 value--; 337 } 338 return value; 339 } 340 341 protected int newLocalMapping(final Type type) { 342 int local = nextLocal; 343 nextLocal += type.getSize(); 344 return local; 345 } 346}