diff options
Diffstat (limited to 'src/cuchaz/enigma/bytecode/ConstPoolEditor.java')
| -rw-r--r-- | src/cuchaz/enigma/bytecode/ConstPoolEditor.java | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java new file mode 100644 index 0000000..aa6149c --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java | |||
| @@ -0,0 +1,316 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.io.DataInputStream; | ||
| 14 | import java.io.DataOutputStream; | ||
| 15 | import java.lang.reflect.Constructor; | ||
| 16 | import java.lang.reflect.Field; | ||
| 17 | import java.lang.reflect.Method; | ||
| 18 | import java.util.HashMap; | ||
| 19 | |||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.Descriptor; | ||
| 22 | import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; | ||
| 23 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 24 | import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; | ||
| 25 | |||
| 26 | public class ConstPoolEditor | ||
| 27 | { | ||
| 28 | private static Method m_getItem; | ||
| 29 | private static Method m_addItem; | ||
| 30 | private static Method m_addItem0; | ||
| 31 | private static Field m_items; | ||
| 32 | private static Field m_cache; | ||
| 33 | private static Field m_numItems; | ||
| 34 | private static Field m_objects; | ||
| 35 | private static Field m_elements; | ||
| 36 | private static Method m_methodWritePool; | ||
| 37 | private static Constructor<ConstPool> m_constructorPool; | ||
| 38 | |||
| 39 | static | ||
| 40 | { | ||
| 41 | try | ||
| 42 | { | ||
| 43 | m_getItem = ConstPool.class.getDeclaredMethod( "getItem", int.class ); | ||
| 44 | m_getItem.setAccessible( true ); | ||
| 45 | |||
| 46 | m_addItem = ConstPool.class.getDeclaredMethod( "addItem", Class.forName( "javassist.bytecode.ConstInfo" ) ); | ||
| 47 | m_addItem.setAccessible( true ); | ||
| 48 | |||
| 49 | m_addItem0 = ConstPool.class.getDeclaredMethod( "addItem0", Class.forName( "javassist.bytecode.ConstInfo" ) ); | ||
| 50 | m_addItem0.setAccessible( true ); | ||
| 51 | |||
| 52 | m_items = ConstPool.class.getDeclaredField( "items" ); | ||
| 53 | m_items.setAccessible( true ); | ||
| 54 | |||
| 55 | m_cache = ConstPool.class.getDeclaredField( "itemsCache" ); | ||
| 56 | m_cache.setAccessible( true ); | ||
| 57 | |||
| 58 | m_numItems = ConstPool.class.getDeclaredField( "numOfItems" ); | ||
| 59 | m_numItems.setAccessible( true ); | ||
| 60 | |||
| 61 | m_objects = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "objects" ); | ||
| 62 | m_objects.setAccessible( true ); | ||
| 63 | |||
| 64 | m_elements = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "elements" ); | ||
| 65 | m_elements.setAccessible( true ); | ||
| 66 | |||
| 67 | m_methodWritePool = ConstPool.class.getDeclaredMethod( "write", DataOutputStream.class ); | ||
| 68 | m_methodWritePool.setAccessible( true ); | ||
| 69 | |||
| 70 | m_constructorPool = ConstPool.class.getDeclaredConstructor( DataInputStream.class ); | ||
| 71 | m_constructorPool.setAccessible( true ); | ||
| 72 | } | ||
| 73 | catch( Exception ex ) | ||
| 74 | { | ||
| 75 | throw new Error( ex ); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | private ConstPool m_pool; | ||
| 80 | |||
| 81 | public ConstPoolEditor( ConstPool pool ) | ||
| 82 | { | ||
| 83 | m_pool = pool; | ||
| 84 | } | ||
| 85 | |||
| 86 | public void writePool( DataOutputStream out ) | ||
| 87 | { | ||
| 88 | try | ||
| 89 | { | ||
| 90 | m_methodWritePool.invoke( m_pool, out ); | ||
| 91 | } | ||
| 92 | catch( Exception ex ) | ||
| 93 | { | ||
| 94 | throw new Error( ex ); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | public static ConstPool readPool( DataInputStream in ) | ||
| 99 | { | ||
| 100 | try | ||
| 101 | { | ||
| 102 | return m_constructorPool.newInstance( in ); | ||
| 103 | } | ||
| 104 | catch( Exception ex ) | ||
| 105 | { | ||
| 106 | throw new Error( ex ); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | public String getMemberrefClassname( int memberrefIndex ) | ||
| 111 | { | ||
| 112 | return Descriptor.toJvmName( m_pool.getClassInfo( m_pool.getMemberClass( memberrefIndex ) ) ); | ||
| 113 | } | ||
| 114 | |||
| 115 | public String getMemberrefName( int memberrefIndex ) | ||
| 116 | { | ||
| 117 | return m_pool.getUtf8Info( m_pool.getNameAndTypeName( m_pool.getMemberNameAndType( memberrefIndex ) ) ); | ||
| 118 | } | ||
| 119 | |||
| 120 | public String getMemberrefType( int memberrefIndex ) | ||
| 121 | { | ||
| 122 | return m_pool.getUtf8Info( m_pool.getNameAndTypeDescriptor( m_pool.getMemberNameAndType( memberrefIndex ) ) ); | ||
| 123 | } | ||
| 124 | |||
| 125 | public ConstInfoAccessor getItem( int index ) | ||
| 126 | { | ||
| 127 | try | ||
| 128 | { | ||
| 129 | Object entry = m_getItem.invoke( m_pool, index ); | ||
| 130 | if( entry == null ) | ||
| 131 | { | ||
| 132 | return null; | ||
| 133 | } | ||
| 134 | return new ConstInfoAccessor( entry ); | ||
| 135 | } | ||
| 136 | catch( Exception ex ) | ||
| 137 | { | ||
| 138 | throw new Error( ex ); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | public int addItem( Object item ) | ||
| 143 | { | ||
| 144 | try | ||
| 145 | { | ||
| 146 | return (Integer)m_addItem.invoke( m_pool, item ); | ||
| 147 | } | ||
| 148 | catch( Exception ex ) | ||
| 149 | { | ||
| 150 | throw new Error( ex ); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | public int addItemForceNew( Object item ) | ||
| 155 | { | ||
| 156 | try | ||
| 157 | { | ||
| 158 | return (Integer)m_addItem0.invoke( m_pool, item ); | ||
| 159 | } | ||
| 160 | catch( Exception ex ) | ||
| 161 | { | ||
| 162 | throw new Error( ex ); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | @SuppressWarnings( "rawtypes" ) | ||
| 166 | public void removeLastItem( ) | ||
| 167 | { | ||
| 168 | try | ||
| 169 | { | ||
| 170 | // remove the item from the cache | ||
| 171 | HashMap cache = getCache(); | ||
| 172 | if( cache != null ) | ||
| 173 | { | ||
| 174 | Object item = getItem( m_pool.getSize() - 1 ); | ||
| 175 | cache.remove( item ); | ||
| 176 | } | ||
| 177 | |||
| 178 | // remove the actual item | ||
| 179 | // based off of LongVector.addElement() | ||
| 180 | Object items = m_items.get( m_pool ); | ||
| 181 | Object[][] objects = (Object[][])m_objects.get( items ); | ||
| 182 | int numElements = (Integer)m_elements.get( items ) - 1; | ||
| 183 | int nth = numElements >> 7; | ||
| 184 | int offset = numElements & (128 - 1); | ||
| 185 | objects[nth][offset] = null; | ||
| 186 | |||
| 187 | // decrement the number of items | ||
| 188 | m_elements.set( items, numElements ); | ||
| 189 | m_numItems.set( m_pool, (Integer)m_numItems.get( m_pool ) - 1 ); | ||
| 190 | } | ||
| 191 | catch( Exception ex ) | ||
| 192 | { | ||
| 193 | throw new Error( ex ); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | @SuppressWarnings( "rawtypes" ) | ||
| 198 | /* TEMP */public HashMap getCache( ) | ||
| 199 | { | ||
| 200 | try | ||
| 201 | { | ||
| 202 | return (HashMap)m_cache.get( m_pool ); | ||
| 203 | } | ||
| 204 | catch( Exception ex ) | ||
| 205 | { | ||
| 206 | throw new Error( ex ); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | @SuppressWarnings( { "rawtypes", "unchecked" } ) | ||
| 211 | public void changeMemberrefNameAndType( int memberrefIndex, String newName, String newType ) | ||
| 212 | { | ||
| 213 | // NOTE: when changing values, we always need to copy-on-write | ||
| 214 | try | ||
| 215 | { | ||
| 216 | // get the memberref item | ||
| 217 | Object item = getItem( memberrefIndex ).getItem(); | ||
| 218 | |||
| 219 | // update the cache | ||
| 220 | HashMap cache = getCache(); | ||
| 221 | if( cache != null ) | ||
| 222 | { | ||
| 223 | cache.remove( item ); | ||
| 224 | } | ||
| 225 | |||
| 226 | new MemberRefInfoAccessor( item ).setNameAndTypeIndex( m_pool.addNameAndTypeInfo( newName, newType ) ); | ||
| 227 | |||
| 228 | // update the cache | ||
| 229 | if( cache != null ) | ||
| 230 | { | ||
| 231 | cache.put( item, item ); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | catch( Exception ex ) | ||
| 235 | { | ||
| 236 | throw new Error( ex ); | ||
| 237 | } | ||
| 238 | |||
| 239 | // make sure the change worked | ||
| 240 | assert( newName.equals( getMemberrefName( memberrefIndex ) ) ); | ||
| 241 | assert( newType.equals( getMemberrefType( memberrefIndex ) ) ); | ||
| 242 | } | ||
| 243 | |||
| 244 | @SuppressWarnings( { "rawtypes", "unchecked" } ) | ||
| 245 | public void changeClassName( int classNameIndex, String newName ) | ||
| 246 | { | ||
| 247 | // NOTE: when changing values, we always need to copy-on-write | ||
| 248 | try | ||
| 249 | { | ||
| 250 | // get the class item | ||
| 251 | Object item = getItem( classNameIndex ).getItem(); | ||
| 252 | |||
| 253 | // update the cache | ||
| 254 | HashMap cache = getCache(); | ||
| 255 | if( cache != null ) | ||
| 256 | { | ||
| 257 | cache.remove( item ); | ||
| 258 | } | ||
| 259 | |||
| 260 | // add the new name and repoint the name-and-type to it | ||
| 261 | new ClassInfoAccessor( item ).setNameIndex( m_pool.addUtf8Info( newName ) ); | ||
| 262 | |||
| 263 | // update the cache | ||
| 264 | if( cache != null ) | ||
| 265 | { | ||
| 266 | cache.put( item, item ); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | catch( Exception ex ) | ||
| 270 | { | ||
| 271 | throw new Error( ex ); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | public static ConstPool newConstPool( ) | ||
| 276 | { | ||
| 277 | // const pool expects the name of a class to initialize itself | ||
| 278 | // but we want an empty pool | ||
| 279 | // so give it a bogus name, and then clear the entries afterwards | ||
| 280 | ConstPool pool = new ConstPool( "a" ); | ||
| 281 | |||
| 282 | ConstPoolEditor editor = new ConstPoolEditor( pool ); | ||
| 283 | int size = pool.getSize(); | ||
| 284 | for( int i=0; i<size-1; i++ ) | ||
| 285 | { | ||
| 286 | editor.removeLastItem(); | ||
| 287 | } | ||
| 288 | |||
| 289 | // make sure the pool is actually empty | ||
| 290 | // although, in this case "empty" means one thing in it | ||
| 291 | // the JVM spec says index 0 should be reserved | ||
| 292 | assert( pool.getSize() == 1 ); | ||
| 293 | assert( editor.getItem( 0 ) == null ); | ||
| 294 | assert( editor.getItem( 1 ) == null ); | ||
| 295 | assert( editor.getItem( 2 ) == null ); | ||
| 296 | assert( editor.getItem( 3 ) == null ); | ||
| 297 | |||
| 298 | // also, clear the cache | ||
| 299 | editor.getCache().clear(); | ||
| 300 | |||
| 301 | return pool; | ||
| 302 | } | ||
| 303 | |||
| 304 | public String dump( ) | ||
| 305 | { | ||
| 306 | StringBuilder buf = new StringBuilder(); | ||
| 307 | for( int i=1; i<m_pool.getSize(); i++ ) | ||
| 308 | { | ||
| 309 | buf.append( String.format( "%4d", i ) ); | ||
| 310 | buf.append( " " ); | ||
| 311 | buf.append( getItem( i ).toString() ); | ||
| 312 | buf.append( "\n" ); | ||
| 313 | } | ||
| 314 | return buf.toString(); | ||
| 315 | } | ||
| 316 | } | ||