summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/bytecode/BytecodeTools.java
diff options
context:
space:
mode:
authorGravatar jeff2014-07-27 22:33:21 -0400
committerGravatar jeff2014-07-27 22:33:21 -0400
commitd7321b5b0d38c575e54c770f7aa18dacbacab3c8 (patch)
treeef4b3e0f83b1fe89125c2674fec023871e70c0d8 /src/cuchaz/enigma/bytecode/BytecodeTools.java
parentmade gui responsive to caret position and show identifier info (diff)
downloadenigma-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.java269
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 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayInputStream;
14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.IOException;
18import java.util.Map;
19import java.util.Set;
20
21import javassist.CtBehavior;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.Bytecode;
24import javassist.bytecode.CodeAttribute;
25import javassist.bytecode.ConstPool;
26import javassist.bytecode.ExceptionTable;
27
28import com.google.common.collect.Maps;
29import com.google.common.collect.Sets;
30
31import cuchaz.enigma.Util;
32import cuchaz.enigma.bytecode.BytecodeIndexIterator.Index;
33import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
34
35public 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}