diff options
| author | 2014-07-27 22:33:21 -0400 | |
|---|---|---|
| committer | 2014-07-27 22:33:21 -0400 | |
| commit | d7321b5b0d38c575e54c770f7aa18dacbacab3c8 (patch) | |
| tree | ef4b3e0f83b1fe89125c2674fec023871e70c0d8 /src/cuchaz/enigma/bytecode/BytecodeTools.java | |
| parent | made gui responsive to caret position and show identifier info (diff) | |
| download | enigma-fork-d7321b5b0d38c575e54c770f7aa18dacbacab3c8.tar.gz enigma-fork-d7321b5b0d38c575e54c770f7aa18dacbacab3c8.tar.xz enigma-fork-d7321b5b0d38c575e54c770f7aa18dacbacab3c8.zip | |
added identifier renaming capability
copied some code over from M3L to handle the heavy bytecode magic.
It's ok... M3L will eventually depend on Enigma.
Completely restructured the mappings though. This way is better. =)
Diffstat (limited to 'src/cuchaz/enigma/bytecode/BytecodeTools.java')
| -rw-r--r-- | src/cuchaz/enigma/bytecode/BytecodeTools.java | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/bytecode/BytecodeTools.java b/src/cuchaz/enigma/bytecode/BytecodeTools.java new file mode 100644 index 0000000..664350e --- /dev/null +++ b/src/cuchaz/enigma/bytecode/BytecodeTools.java | |||
| @@ -0,0 +1,269 @@ | |||
| 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.ByteArrayInputStream; | ||
| 14 | import java.io.ByteArrayOutputStream; | ||
| 15 | import java.io.DataInputStream; | ||
| 16 | import java.io.DataOutputStream; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.util.Map; | ||
| 19 | import java.util.Set; | ||
| 20 | |||
| 21 | import javassist.CtBehavior; | ||
| 22 | import javassist.bytecode.BadBytecode; | ||
| 23 | import javassist.bytecode.Bytecode; | ||
| 24 | import javassist.bytecode.CodeAttribute; | ||
| 25 | import javassist.bytecode.ConstPool; | ||
| 26 | import javassist.bytecode.ExceptionTable; | ||
| 27 | |||
| 28 | import com.google.common.collect.Maps; | ||
| 29 | import com.google.common.collect.Sets; | ||
| 30 | |||
| 31 | import cuchaz.enigma.Util; | ||
| 32 | import cuchaz.enigma.bytecode.BytecodeIndexIterator.Index; | ||
| 33 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 34 | |||
| 35 | public class BytecodeTools | ||
| 36 | { | ||
| 37 | public static byte[] writeBytecode( Bytecode bytecode ) | ||
| 38 | throws IOException | ||
| 39 | { | ||
| 40 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); | ||
| 41 | DataOutputStream out = new DataOutputStream( buf ); | ||
| 42 | try | ||
| 43 | { | ||
| 44 | // write the constant pool | ||
| 45 | new ConstPoolEditor( bytecode.getConstPool() ).writePool( out ); | ||
| 46 | |||
| 47 | // write metadata | ||
| 48 | out.writeShort( bytecode.getMaxStack() ); | ||
| 49 | out.writeShort( bytecode.getMaxLocals() ); | ||
| 50 | out.writeShort( bytecode.getStackDepth() ); | ||
| 51 | |||
| 52 | // write the code | ||
| 53 | out.writeShort( bytecode.getSize() ); | ||
| 54 | out.write( bytecode.get() ); | ||
| 55 | |||
| 56 | // write the exception table | ||
| 57 | int numEntries = bytecode.getExceptionTable().size(); | ||
| 58 | out.writeShort( numEntries ); | ||
| 59 | for( int i=0; i<numEntries; i++ ) | ||
| 60 | { | ||
| 61 | out.writeShort( bytecode.getExceptionTable().startPc( i ) ); | ||
| 62 | out.writeShort( bytecode.getExceptionTable().endPc( i ) ); | ||
| 63 | out.writeShort( bytecode.getExceptionTable().handlerPc( i ) ); | ||
| 64 | out.writeShort( bytecode.getExceptionTable().catchType( i ) ); | ||
| 65 | } | ||
| 66 | |||
| 67 | out.close(); | ||
| 68 | return buf.toByteArray(); | ||
| 69 | } | ||
| 70 | catch( Exception ex ) | ||
| 71 | { | ||
| 72 | Util.closeQuietly( out ); | ||
| 73 | throw new Error( ex ); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | public static Bytecode readBytecode( byte[] bytes ) | ||
| 78 | throws IOException | ||
| 79 | { | ||
| 80 | ByteArrayInputStream buf = new ByteArrayInputStream( bytes ); | ||
| 81 | DataInputStream in = new DataInputStream( buf ); | ||
| 82 | try | ||
| 83 | { | ||
| 84 | // read the constant pool entries and update the class | ||
| 85 | ConstPool pool = ConstPoolEditor.readPool( in ); | ||
| 86 | |||
| 87 | // read metadata | ||
| 88 | int maxStack = in.readShort(); | ||
| 89 | int maxLocals = in.readShort(); | ||
| 90 | int stackDepth = in.readShort(); | ||
| 91 | |||
| 92 | Bytecode bytecode = new Bytecode( pool, maxStack, maxLocals ); | ||
| 93 | bytecode.setStackDepth( stackDepth ); | ||
| 94 | |||
| 95 | // read the code | ||
| 96 | int size = in.readShort(); | ||
| 97 | byte[] code = new byte[size]; | ||
| 98 | in.read( code ); | ||
| 99 | setBytecode( bytecode, code ); | ||
| 100 | |||
| 101 | // read the exception table | ||
| 102 | int numEntries = in.readShort(); | ||
| 103 | for( int i=0; i<numEntries; i++ ) | ||
| 104 | { | ||
| 105 | bytecode.getExceptionTable().add( in.readShort(), in.readShort(), in.readShort(), in.readShort() ); | ||
| 106 | } | ||
| 107 | |||
| 108 | in.close(); | ||
| 109 | return bytecode; | ||
| 110 | } | ||
| 111 | catch( Exception ex ) | ||
| 112 | { | ||
| 113 | Util.closeQuietly( in ); | ||
| 114 | throw new Error( ex ); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | public static Bytecode prepareMethodForBytecode( CtBehavior behavior, Bytecode bytecode ) | ||
| 119 | throws BadBytecode | ||
| 120 | { | ||
| 121 | // update the destination class const pool | ||
| 122 | bytecode = copyBytecodeToConstPool( behavior.getMethodInfo().getConstPool(), bytecode ); | ||
| 123 | |||
| 124 | // update method locals and stack | ||
| 125 | CodeAttribute attribute = behavior.getMethodInfo().getCodeAttribute(); | ||
| 126 | if( bytecode.getMaxLocals() > attribute.getMaxLocals() ) | ||
| 127 | { | ||
| 128 | attribute.setMaxLocals( bytecode.getMaxLocals() ); | ||
| 129 | } | ||
| 130 | if( bytecode.getMaxStack() > attribute.getMaxStack() ) | ||
| 131 | { | ||
| 132 | attribute.setMaxStack( bytecode.getMaxStack() ); | ||
| 133 | } | ||
| 134 | |||
| 135 | return bytecode; | ||
| 136 | } | ||
| 137 | |||
| 138 | public static Bytecode copyBytecodeToConstPool( ConstPool dest, Bytecode bytecode ) | ||
| 139 | throws BadBytecode | ||
| 140 | { | ||
| 141 | // get the entries this bytecode needs from the const pool | ||
| 142 | Set<Integer> indices = Sets.newTreeSet(); | ||
| 143 | ConstPoolEditor editor = new ConstPoolEditor( bytecode.getConstPool() ); | ||
| 144 | BytecodeIndexIterator iterator = new BytecodeIndexIterator( bytecode ); | ||
| 145 | for( Index index : iterator.indices() ) | ||
| 146 | { | ||
| 147 | assert( index.isValid( bytecode ) ); | ||
| 148 | InfoType.gatherIndexTree( indices, editor, index.getIndex() ); | ||
| 149 | } | ||
| 150 | |||
| 151 | Map<Integer,Integer> indexMap = Maps.newTreeMap(); | ||
| 152 | |||
| 153 | ConstPool src = bytecode.getConstPool(); | ||
| 154 | ConstPoolEditor editorSrc = new ConstPoolEditor( src ); | ||
| 155 | ConstPoolEditor editorDest = new ConstPoolEditor( dest ); | ||
| 156 | |||
| 157 | // copy entries over in order of level so the index mapping is easier | ||
| 158 | for( InfoType type : InfoType.getSortedByLevel() ) | ||
| 159 | { | ||
| 160 | for( int index : indices ) | ||
| 161 | { | ||
| 162 | ConstInfoAccessor entry = editorSrc.getItem( index ); | ||
| 163 | |||
| 164 | // skip entries that aren't this type | ||
| 165 | if( entry.getType() != type ) | ||
| 166 | { | ||
| 167 | continue; | ||
| 168 | } | ||
| 169 | |||
| 170 | // make sure the source entry is valid before we copy it | ||
| 171 | assert( type.subIndicesAreValid( entry, editorSrc ) ); | ||
| 172 | assert( type.selfIndexIsValid( entry, editorSrc ) ); | ||
| 173 | |||
| 174 | // make a copy of the entry so we can modify it safely | ||
| 175 | ConstInfoAccessor entryCopy = editorSrc.getItem( index ).copy(); | ||
| 176 | assert( type.subIndicesAreValid( entryCopy, editorSrc ) ); | ||
| 177 | assert( type.selfIndexIsValid( entryCopy, editorSrc ) ); | ||
| 178 | |||
| 179 | // remap the indices | ||
| 180 | type.remapIndices( indexMap, entryCopy ); | ||
| 181 | assert( type.subIndicesAreValid( entryCopy, editorDest ) ); | ||
| 182 | |||
| 183 | // put the copy in the destination pool | ||
| 184 | int newIndex = editorDest.addItem( entryCopy.getItem() ); | ||
| 185 | entryCopy.setIndex( newIndex ); | ||
| 186 | assert( type.selfIndexIsValid( entryCopy, editorDest ) ) : type + ", self: " + entryCopy + " dest: " + editorDest.getItem( entryCopy.getIndex() ); | ||
| 187 | |||
| 188 | // make sure the source entry is unchanged | ||
| 189 | assert( type.subIndicesAreValid( entry, editorSrc ) ); | ||
| 190 | assert( type.selfIndexIsValid( entry, editorSrc ) ); | ||
| 191 | |||
| 192 | // add the index mapping so we can update the bytecode later | ||
| 193 | if( indexMap.containsKey( index ) ) | ||
| 194 | { | ||
| 195 | throw new Error( "Entry at index " + index + " already copied!" ); | ||
| 196 | } | ||
| 197 | indexMap.put( index, newIndex ); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | // make a new bytecode | ||
| 202 | Bytecode newBytecode = new Bytecode( dest, bytecode.getMaxStack(), bytecode.getMaxLocals() ); | ||
| 203 | bytecode.setStackDepth( bytecode.getStackDepth() ); | ||
| 204 | setBytecode( newBytecode, bytecode.get() ); | ||
| 205 | setExceptionTable( newBytecode, bytecode.getExceptionTable() ); | ||
| 206 | |||
| 207 | // apply the mappings to the bytecode | ||
| 208 | BytecodeIndexIterator iter = new BytecodeIndexIterator( newBytecode ); | ||
| 209 | for( Index index : iter.indices() ) | ||
| 210 | { | ||
| 211 | int oldIndex = index.getIndex(); | ||
| 212 | Integer newIndex = indexMap.get( oldIndex ); | ||
| 213 | if( newIndex != null ) | ||
| 214 | { | ||
| 215 | // make sure this mapping makes sense | ||
| 216 | InfoType typeSrc = editorSrc.getItem( oldIndex ).getType(); | ||
| 217 | InfoType typeDest = editorDest.getItem( newIndex ).getType(); | ||
| 218 | assert( typeSrc == typeDest ); | ||
| 219 | |||
| 220 | // apply the mapping | ||
| 221 | index.setIndex( newIndex ); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | iter.saveChangesToBytecode(); | ||
| 225 | |||
| 226 | // make sure all the indices are valid | ||
| 227 | iter = new BytecodeIndexIterator( newBytecode ); | ||
| 228 | for( Index index : iter.indices() ) | ||
| 229 | { | ||
| 230 | assert( index.isValid( newBytecode ) ); | ||
| 231 | } | ||
| 232 | |||
| 233 | return newBytecode; | ||
| 234 | } | ||
| 235 | |||
| 236 | public static void setBytecode( Bytecode dest, byte[] src ) | ||
| 237 | { | ||
| 238 | if( src.length > dest.getSize() ) | ||
| 239 | { | ||
| 240 | dest.addGap( src.length - dest.getSize() ); | ||
| 241 | } | ||
| 242 | assert( dest.getSize() == src.length ); | ||
| 243 | for( int i=0; i<src.length; i++ ) | ||
| 244 | { | ||
| 245 | dest.write( i, src[i] ); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | public static void setExceptionTable( Bytecode dest, ExceptionTable src ) | ||
| 250 | { | ||
| 251 | // clear the dest exception table | ||
| 252 | int size = dest.getExceptionTable().size(); | ||
| 253 | for( int i=size-1; i>=0; i-- ) | ||
| 254 | { | ||
| 255 | dest.getExceptionTable().remove( i ); | ||
| 256 | } | ||
| 257 | |||
| 258 | // copy the exception table | ||
| 259 | for( int i=0; i<src.size(); i++ ) | ||
| 260 | { | ||
| 261 | dest.getExceptionTable().add( | ||
| 262 | src.startPc( i ), | ||
| 263 | src.endPc( i ), | ||
| 264 | src.handlerPc( i ), | ||
| 265 | src.catchType( i ) | ||
| 266 | ); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||