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 the {@code 094 * ASM}<i>x</i> values in {@link Opcodes}. 095 * @param access access flags of the adapted method. 096 * @param descriptor the method's descriptor (see {@link Type}). 097 * @param methodVisitor the method visitor to which this adapter delegates calls. 098 */ 099 protected LocalVariablesSorter( 100 final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) { 101 super(api, methodVisitor); 102 nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0; 103 for (Type argumentType : Type.getArgumentTypes(descriptor)) { 104 nextLocal += argumentType.getSize(); 105 } 106 firstLocal = nextLocal; 107 } 108 109 @Override 110 public void visitVarInsn(final int opcode, final int varIndex) { 111 Type varType; 112 switch (opcode) { 113 case Opcodes.LLOAD: 114 case Opcodes.LSTORE: 115 varType = Type.LONG_TYPE; 116 break; 117 case Opcodes.DLOAD: 118 case Opcodes.DSTORE: 119 varType = Type.DOUBLE_TYPE; 120 break; 121 case Opcodes.FLOAD: 122 case Opcodes.FSTORE: 123 varType = Type.FLOAT_TYPE; 124 break; 125 case Opcodes.ILOAD: 126 case Opcodes.ISTORE: 127 varType = Type.INT_TYPE; 128 break; 129 case Opcodes.ALOAD: 130 case Opcodes.ASTORE: 131 case Opcodes.RET: 132 varType = OBJECT_TYPE; 133 break; 134 default: 135 throw new IllegalArgumentException("Invalid opcode " + opcode); 136 } 137 super.visitVarInsn(opcode, remap(varIndex, varType)); 138 } 139 140 @Override 141 public void visitIincInsn(final int varIndex, final int increment) { 142 super.visitIincInsn(remap(varIndex, Type.INT_TYPE), increment); 143 } 144 145 @Override 146 public void visitMaxs(final int maxStack, final int maxLocals) { 147 super.visitMaxs(maxStack, nextLocal); 148 } 149 150 @Override 151 public void visitLocalVariable( 152 final String name, 153 final String descriptor, 154 final String signature, 155 final Label start, 156 final Label end, 157 final int index) { 158 int remappedIndex = remap(index, Type.getType(descriptor)); 159 super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex); 160 } 161 162 @Override 163 public AnnotationVisitor visitLocalVariableAnnotation( 164 final int typeRef, 165 final TypePath typePath, 166 final Label[] start, 167 final Label[] end, 168 final int[] index, 169 final String descriptor, 170 final boolean visible) { 171 Type type = Type.getType(descriptor); 172 int[] remappedIndex = new int[index.length]; 173 for (int i = 0; i < remappedIndex.length; ++i) { 174 remappedIndex[i] = remap(index[i], type); 175 } 176 return super.visitLocalVariableAnnotation( 177 typeRef, typePath, start, end, remappedIndex, descriptor, visible); 178 } 179 180 @Override 181 public void visitFrame( 182 final int type, 183 final int numLocal, 184 final Object[] local, 185 final int numStack, 186 final Object[] stack) { 187 if (type != Opcodes.F_NEW) { // Uncompressed frame. 188 throw new IllegalArgumentException( 189 "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)"); 190 } 191 192 // Create a copy of remappedLocals. 193 Object[] oldRemappedLocals = new Object[remappedLocalTypes.length]; 194 System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length); 195 196 updateNewLocals(remappedLocalTypes); 197 198 // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the 199 // variables added with 'newLocal'. 200 int oldVar = 0; // Old local variable index. 201 for (int i = 0; i < numLocal; ++i) { 202 Object localType = local[i]; 203 if (localType != Opcodes.TOP) { 204 Type varType = OBJECT_TYPE; 205 if (localType == Opcodes.INTEGER) { 206 varType = Type.INT_TYPE; 207 } else if (localType == Opcodes.FLOAT) { 208 varType = Type.FLOAT_TYPE; 209 } else if (localType == Opcodes.LONG) { 210 varType = Type.LONG_TYPE; 211 } else if (localType == Opcodes.DOUBLE) { 212 varType = Type.DOUBLE_TYPE; 213 } else if (localType instanceof String) { 214 varType = Type.getObjectType((String) localType); 215 } 216 setFrameLocal(remap(oldVar, varType), localType); 217 } 218 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 219 } 220 221 // Remove TOP after long and double types as well as trailing TOPs. 222 oldVar = 0; 223 int newVar = 0; 224 int remappedNumLocal = 0; 225 while (oldVar < remappedLocalTypes.length) { 226 Object localType = remappedLocalTypes[oldVar]; 227 oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1; 228 if (localType != null && localType != Opcodes.TOP) { 229 remappedLocalTypes[newVar++] = localType; 230 remappedNumLocal = newVar; 231 } else { 232 remappedLocalTypes[newVar++] = Opcodes.TOP; 233 } 234 } 235 236 // Visit the remapped frame. 237 super.visitFrame(type, remappedNumLocal, remappedLocalTypes, numStack, stack); 238 239 // Restore the original value of 'remappedLocals'. 240 remappedLocalTypes = oldRemappedLocals; 241 } 242 243 // ----------------------------------------------------------------------------------------------- 244 245 /** 246 * Constructs a new local variable of the given type. 247 * 248 * @param type the type of the local variable to be created. 249 * @return the identifier of the newly created local variable. 250 */ 251 public int newLocal(final Type type) { 252 Object localType; 253 switch (type.getSort()) { 254 case Type.BOOLEAN: 255 case Type.CHAR: 256 case Type.BYTE: 257 case Type.SHORT: 258 case Type.INT: 259 localType = Opcodes.INTEGER; 260 break; 261 case Type.FLOAT: 262 localType = Opcodes.FLOAT; 263 break; 264 case Type.LONG: 265 localType = Opcodes.LONG; 266 break; 267 case Type.DOUBLE: 268 localType = Opcodes.DOUBLE; 269 break; 270 case Type.ARRAY: 271 localType = type.getDescriptor(); 272 break; 273 case Type.OBJECT: 274 localType = type.getInternalName(); 275 break; 276 default: 277 throw new AssertionError(); 278 } 279 int local = newLocalMapping(type); 280 setLocalType(local, type); 281 setFrameLocal(local, localType); 282 return local; 283 } 284 285 /** 286 * Notifies subclasses that a new stack map frame is being visited. The array argument contains 287 * the stack map frame types corresponding to the local variables added with {@link #newLocal}. 288 * This method can update these types in place for the stack map frame being visited. The default 289 * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal} 290 * will have the same type in all stack map frames. But this behavior is not always the desired 291 * one, for instance if a local variable is added in the middle of a try/catch block: the frame 292 * for the exception handler should have a TOP type for this new local. 293 * 294 * @param newLocals the stack map frame types corresponding to the local variables added with 295 * {@link #newLocal} (and null for the others). The format of this array is the same as in 296 * {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The 297 * types for the current stack map frame must be updated in place in this array. 298 */ 299 protected void updateNewLocals(final Object[] newLocals) { 300 // The default implementation does nothing. 301 } 302 303 /** 304 * Notifies subclasses that a local variable has been added or remapped. The default 305 * implementation of this method does nothing. 306 * 307 * @param local a local variable identifier, as returned by {@link #newLocal}. 308 * @param type the type of the value being stored in the local variable. 309 */ 310 protected void setLocalType(final int local, final Type type) { 311 // The default implementation does nothing. 312 } 313 314 private void setFrameLocal(final int local, final Object type) { 315 int numLocals = remappedLocalTypes.length; 316 if (local >= numLocals) { 317 Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)]; 318 System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals); 319 remappedLocalTypes = newRemappedLocalTypes; 320 } 321 remappedLocalTypes[local] = type; 322 } 323 324 private int remap(final int varIndex, final Type type) { 325 if (varIndex + type.getSize() <= firstLocal) { 326 return varIndex; 327 } 328 int key = 2 * varIndex + type.getSize() - 1; 329 int size = remappedVariableIndices.length; 330 if (key >= size) { 331 int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)]; 332 System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size); 333 remappedVariableIndices = newRemappedVariableIndices; 334 } 335 int value = remappedVariableIndices[key]; 336 if (value == 0) { 337 value = newLocalMapping(type); 338 setLocalType(value, type); 339 remappedVariableIndices[key] = value + 1; 340 } else { 341 value--; 342 } 343 return value; 344 } 345 346 protected int newLocalMapping(final Type type) { 347 int local = nextLocal; 348 nextLocal += type.getSize(); 349 return local; 350 } 351}