/******************************************************************************* * Copyright (c) 2014 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma.bytecode; import java.io.DataInputStream; import java.io.DataOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; public class ConstPoolEditor { private static Method m_getItem; private static Method m_addItem; private static Method m_addItem0; private static Field m_items; private static Field m_cache; private static Field m_numItems; private static Field m_objects; private static Field m_elements; private static Method m_methodWritePool; private static Constructor m_constructorPool; static { try { m_getItem = ConstPool.class.getDeclaredMethod( "getItem", int.class ); m_getItem.setAccessible( true ); m_addItem = ConstPool.class.getDeclaredMethod( "addItem", Class.forName( "javassist.bytecode.ConstInfo" ) ); m_addItem.setAccessible( true ); m_addItem0 = ConstPool.class.getDeclaredMethod( "addItem0", Class.forName( "javassist.bytecode.ConstInfo" ) ); m_addItem0.setAccessible( true ); m_items = ConstPool.class.getDeclaredField( "items" ); m_items.setAccessible( true ); m_cache = ConstPool.class.getDeclaredField( "itemsCache" ); m_cache.setAccessible( true ); m_numItems = ConstPool.class.getDeclaredField( "numOfItems" ); m_numItems.setAccessible( true ); m_objects = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "objects" ); m_objects.setAccessible( true ); m_elements = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "elements" ); m_elements.setAccessible( true ); m_methodWritePool = ConstPool.class.getDeclaredMethod( "write", DataOutputStream.class ); m_methodWritePool.setAccessible( true ); m_constructorPool = ConstPool.class.getDeclaredConstructor( DataInputStream.class ); m_constructorPool.setAccessible( true ); } catch( Exception ex ) { throw new Error( ex ); } } private ConstPool m_pool; public ConstPoolEditor( ConstPool pool ) { m_pool = pool; } public void writePool( DataOutputStream out ) { try { m_methodWritePool.invoke( m_pool, out ); } catch( Exception ex ) { throw new Error( ex ); } } public static ConstPool readPool( DataInputStream in ) { try { return m_constructorPool.newInstance( in ); } catch( Exception ex ) { throw new Error( ex ); } } public String getMemberrefClassname( int memberrefIndex ) { return Descriptor.toJvmName( m_pool.getClassInfo( m_pool.getMemberClass( memberrefIndex ) ) ); } public String getMemberrefName( int memberrefIndex ) { return m_pool.getUtf8Info( m_pool.getNameAndTypeName( m_pool.getMemberNameAndType( memberrefIndex ) ) ); } public String getMemberrefType( int memberrefIndex ) { return m_pool.getUtf8Info( m_pool.getNameAndTypeDescriptor( m_pool.getMemberNameAndType( memberrefIndex ) ) ); } public ConstInfoAccessor getItem( int index ) { try { Object entry = m_getItem.invoke( m_pool, index ); if( entry == null ) { return null; } return new ConstInfoAccessor( entry ); } catch( Exception ex ) { throw new Error( ex ); } } public int addItem( Object item ) { try { return (Integer)m_addItem.invoke( m_pool, item ); } catch( Exception ex ) { throw new Error( ex ); } } public int addItemForceNew( Object item ) { try { return (Integer)m_addItem0.invoke( m_pool, item ); } catch( Exception ex ) { throw new Error( ex ); } } @SuppressWarnings( "rawtypes" ) public void removeLastItem( ) { try { // remove the item from the cache HashMap cache = getCache(); if( cache != null ) { Object item = getItem( m_pool.getSize() - 1 ); cache.remove( item ); } // remove the actual item // based off of LongVector.addElement() Object items = m_items.get( m_pool ); Object[][] objects = (Object[][])m_objects.get( items ); int numElements = (Integer)m_elements.get( items ) - 1; int nth = numElements >> 7; int offset = numElements & (128 - 1); objects[nth][offset] = null; // decrement the number of items m_elements.set( items, numElements ); m_numItems.set( m_pool, (Integer)m_numItems.get( m_pool ) - 1 ); } catch( Exception ex ) { throw new Error( ex ); } } @SuppressWarnings( "rawtypes" ) /* TEMP */public HashMap getCache( ) { try { return (HashMap)m_cache.get( m_pool ); } catch( Exception ex ) { throw new Error( ex ); } } @SuppressWarnings( { "rawtypes", "unchecked" } ) public void changeMemberrefNameAndType( int memberrefIndex, String newName, String newType ) { // NOTE: when changing values, we always need to copy-on-write try { // get the memberref item Object item = getItem( memberrefIndex ).getItem(); // update the cache HashMap cache = getCache(); if( cache != null ) { cache.remove( item ); } new MemberRefInfoAccessor( item ).setNameAndTypeIndex( m_pool.addNameAndTypeInfo( newName, newType ) ); // update the cache if( cache != null ) { cache.put( item, item ); } } catch( Exception ex ) { throw new Error( ex ); } // make sure the change worked assert( newName.equals( getMemberrefName( memberrefIndex ) ) ); assert( newType.equals( getMemberrefType( memberrefIndex ) ) ); } @SuppressWarnings( { "rawtypes", "unchecked" } ) public void changeClassName( int classNameIndex, String newName ) { // NOTE: when changing values, we always need to copy-on-write try { // get the class item Object item = getItem( classNameIndex ).getItem(); // update the cache HashMap cache = getCache(); if( cache != null ) { cache.remove( item ); } // add the new name and repoint the name-and-type to it new ClassInfoAccessor( item ).setNameIndex( m_pool.addUtf8Info( newName ) ); // update the cache if( cache != null ) { cache.put( item, item ); } } catch( Exception ex ) { throw new Error( ex ); } } public static ConstPool newConstPool( ) { // const pool expects the name of a class to initialize itself // but we want an empty pool // so give it a bogus name, and then clear the entries afterwards ConstPool pool = new ConstPool( "a" ); ConstPoolEditor editor = new ConstPoolEditor( pool ); int size = pool.getSize(); for( int i=0; i