summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/bytecode/BytecodeTools.java
diff options
context:
space:
mode:
Diffstat (limited to '')
-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}