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.AnnotationVisitor; 031import io.ebean.enhance.asm.Label; 032import io.ebean.enhance.asm.MethodVisitor; 033import io.ebean.enhance.asm.Opcodes; 034import io.ebean.enhance.asm.Type; 035import io.ebean.enhance.asm.TypePath; 036 037/** 038 * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter 039 * allows one to easily add new local variables to a method. It may be used by inheriting from this 040 * class, but the preferred way of using it is via delegation: the next visitor in the chain can 041 * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a 042 * reference back to this {@link LocalVariablesSorter}). 043 * 044 * @author Chris Nokleberg 045 * @author Eugene Kuleshov 046 * @author Eric Bruneton 047 */ 048public class LocalVariablesSorter extends MethodVisitor { 049 050 /** The type of the java.lang.Object class. */ 051 private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); 052 053 /** 054 * The mapping from old to new local variable indices. A local variable at index i of size 1 is 055 * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to 056 * 'mapping[2*i+1]'. 057 */ 058 private int[] remappedVariableIndices = new int[40]; 059 060 /** 061 * The local variable types after remapping. The format of this array is the same as in {@link 062 * MethodVisitor#visitFrame}, except that long and double types use two slots. 063 */ 064 private Object[] remappedLocalTypes = new Object[20]; 065 066 /** The index of the first local variable, after formal parameters. */ 067 protected final int firstLocal; 068 069 /** The index of the next local variable to be created by {@link #newLocal}. */ 070 protected int nextLocal; 071 072 /** 073 * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>. 074 * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)} 075 * version. 076 * 077 * @param access access flags of the adapted method. 078 * @param descriptor the method's descriptor (see {@link Type}). 079 * @param methodVisitor the method visitor to which this adapter delegates calls. 080 * @throws IllegalStateException if a subclass calls this constructor. 081 */ 082 public LocalVariablesSorter( 083 final int access, final String descriptor, final MethodVisitor methodVisitor) { 084 this(/* latest api = */ Opcodes.ASM9, access, descriptor, methodVisitor); 085 if (getClass() != LocalVariablesSorter.class) { 086 throw new IllegalStateException(); 087 } 088 } 089 090 /** 091 * Constructs a new {@link LocalVariablesSorter}. 092 * 093 * @param api the ASM API version implemented by this visitor. Must be one of {@link 094 * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link 095 * Opcodes#ASM8} or {@link Opcodes#ASM9}. 096 * @param access access flags of the adapted method. 097 * @param descriptor the method's descriptor (see {@link Type}). 098 * @param methodVisitor the method visitor to which this adapter delegates calls. 099 */ 100 protected LocalVariablesSorter( 101 final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) { 102 super(api, methodVisitor); 103 nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0; 104 for (Type argumentType : Type.getArgumentTypes(descriptor)) { 105 nextLocal += argumentType.getSize(); 106 } 107 firstLocal = nextLocal; 108 } 109 110 @Override 111 public void visitVarInsn(final int opcode, final int var) { 112 Type varType; 113 switch (opcode) { 114 case Opcodes.LLOAD: 115 case Opcodes.LSTORE: 116 varType = Type.LONG_TYPE; 117 break; 118 case Opcodes.DLOAD: 119 case Opcodes.DSTORE: 120 varType = Type.DOUBLE_TYPE; 121 break; 122 case Opcodes.FLOAD: 123 case Opcodes.FSTORE: 124 varType = Type.FLOAT_TYPE; 125 break; 126 case Opcodes.ILOAD: 127 case Opcodes.ISTORE: 128 varType = Type.INT_TYPE; 129 break; 130 case Opcodes.ALOAD: 131 case Opcodes.ASTORE: 132 case Opcodes.RET: 133 varType = OBJECT_TYPE; 134 break; 135 default: 136 throw new IllegalArgumentException("Invalid opcode " + opcode); 137 } 138 super.visitVarInsn(opcode, remap(var, varType)); 139 } 140 141 @Override 142 public void visitIincInsn(final int var, final int increment) { 143 super.visitIincInsn(remap(var, Type.INT_TYPE), increment); 144 } 145 146 @Override 147 public void visitMaxs(final int maxStack, final int maxLocals) { 148 super.visitMaxs(maxStack, nextLocal); 149 } 150 151 @Override 152 public void visitLocalVariable( 153 final String name, 154 final String descriptor, 155 final String signature, 156 final Label start, 157 final Label end, 158 final int index) { 159 int remappedIndex = remap(index, Type.getType(descriptor)); 160 super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex); 161 } 162 163 @Override 164 public AnnotationVisitor visitLocalVariableAnnotation( 165 final int typeRef, 166 final TypePath typePath, 167 final Label[] start, 168 final Label[] end, 169 final int[] index, 170 final String descriptor, 171 final boolean visible) { 172 Type type = Type.getType(descriptor); 173 int[] remappedIndex = new int[index.length]; 174 for (int i = 0; i < remappedIndex.length; ++i) { 175 remappedIndex[i] = remap(index[i], type); 176 } 177 return super.visitLocalVariableAnnotation( 178 typeRef, typePath, start, end, remappedIndex, descriptor, visible); 179 } 180 181 @Override 182 public void visitFrame( 183 final int type, 184 final int numLocal, 185 final Object[] local, 186 final int numStack, 187 final Object[] stack) { 188 if (type != Opcodes.F_NEW) { // Uncompressed frame. 189 throw new IllegalArgumentException( 190 "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)"); 191 } 192 193 // Create a copy of remappedLocals. 194 Object[] oldRemappedLocals = new Object[remappedLocalTypes.length]; 195 System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length); 196 197 updateNewLocals(remappedLocalTypes); 198 199 // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the 200 // variables added with 'newLocal'. 201 int oldVar = 0; // Old local variable index. 202 for (int i = 0; i < numLocal; ++i) { 203 Object localType = local[i]; 204 if (localType != Opcodes.TOP) { 205 Type varType = OBJECT_TYPE; 206 if (localType == Opcodes.INTEGER) { 207 varType = Type.INT_TYPE; 208 } else if (localType == Opcodes.FLOAT) { 209 varType = Type.FLOAT_TYPE; 210 } else if (localType == Opcodes.LONG) { 211 varType = Type.LONG_TYPE; 212 } else if (localType == Opcodes.DOUBLE) { 213 varType = Type.DOUBLE_TYPE; 214 } else if (localType instanceof String) { 215 varType = Type.getObjectType((String) localType); 216 } 217 setFrameLocal(remap(oldVar, varType), localType); 218 } 219 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 220 } 221 222 // Remove TOP after long and double types as well as trailing TOPs. 223 oldVar = 0; 224 int newVar = 0; 225 int remappedNumLocal = 0; 226 while (oldVar < remappedLocalTypes.length) { 227 Object localType = remappedLocalTypes[oldVar]; 228 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 229 if (localType != null && localType != Opcodes.TOP) { 230 remappedLocalTypes[newVar++] = localType; 231 remappedNumLocal = newVar; 232 } else { 233 remappedLocalTypes[newVar++] = Opcodes.TOP; 234 } 235 } 236 237 // Visit the remapped frame. 238 super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack); 239 240 // Restore the original value of 'remappedLocals'. 241 remappedLocalTypes = oldRemappedLocals; 242 } 243 244 // ----------------------------------------------------------------------------------------------- 245 246 /** 247 * Constructs a new local variable of the given type. 248 * 249 * @param type the type of the local variable to be created. 250 * @return the identifier of the newly created local variable. 251 */ 252 public int newLocal(final Type type) { 253 Object localType; 254 switch (type.getSort()) { 255 case Type.BOOLEAN: 256 case Type.CHAR: 257 case Type.BYTE: 258 case Type.SHORT: 259 case Type.INT: 260 localType = Opcodes.INTEGER; 261 break; 262 case Type.FLOAT: 263 localType = Opcodes.FLOAT; 264 break; 265 case Type.LONG: 266 localType = Opcodes.LONG; 267 break; 268 case Type.DOUBLE: 269 localType = Opcodes.DOUBLE; 270 break; 271 case Type.ARRAY: 272 localType = type.getDescriptor(); 273 break; 274 case Type.OBJECT: 275 localType = type.getInternalName(); 276 break; 277 default: 278 throw new AssertionError(); 279 } 280 int local = newLocalMapping(type); 281 setLocalType(local, type); 282 setFrameLocal(local, localType); 283 return local; 284 } 285 286 /** 287 * Notifies subclasses that a new stack map frame is being visited. The array argument contains 288 * the stack map frame types corresponding to the local variables added with {@link #newLocal}. 289 * This method can update these types in place for the stack map frame being visited. The default 290 * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal} 291 * will have the same type in all stack map frames. But this behavior is not always the desired 292 * one, for instance if a local variable is added in the middle of a try/catch block: the frame 293 * for the exception handler should have a TOP type for this new local. 294 * 295 * @param newLocals the stack map frame types corresponding to the local variables added with 296 * {@link #newLocal} (and null for the others). The format of this array is the same as in 297 * {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The 298 * types for the current stack map frame must be updated in place in this array. 299 */ 300 protected void updateNewLocals(final Object[] newLocals) { 301 // The default implementation does nothing. 302 } 303 304 /** 305 * Notifies subclasses that a local variable has been added or remapped. The default 306 * implementation of this method does nothing. 307 * 308 * @param local a local variable identifier, as returned by {@link #newLocal}. 309 * @param type the type of the value being stored in the local variable. 310 */ 311 protected void setLocalType(final int local, final Type type) { 312 // The default implementation does nothing. 313 } 314 315 private void setFrameLocal(final int local, final Object type) { 316 int numLocals = remappedLocalTypes.length; 317 if (local >= numLocals) { 318 Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)]; 319 System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals); 320 remappedLocalTypes = newRemappedLocalTypes; 321 } 322 remappedLocalTypes[local] = type; 323 } 324 325 private int remap(final int var, final Type type) { 326 if (var + type.getSize() <= firstLocal) { 327 return var; 328 } 329 int key = 2 * var + type.getSize() - 1; 330 int size = remappedVariableIndices.length; 331 if (key >= size) { 332 int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)]; 333 System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size); 334 remappedVariableIndices = newRemappedVariableIndices; 335 } 336 int value = remappedVariableIndices[key]; 337 if (value == 0) { 338 value = newLocalMapping(type); 339 setLocalType(value, type); 340 remappedVariableIndices[key] = value + 1; 341 } else { 342 value--; 343 } 344 return value; 345 } 346 347 protected int newLocalMapping(final Type type) { 348 int local = nextLocal; 349 nextLocal += type.getSize(); 350 return local; 351 } 352}