summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/analysis/JarIndex.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java187
1 files changed, 113 insertions, 74 deletions
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
index 845be60..9962bfa 100644
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -10,27 +10,21 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.analysis; 11package cuchaz.enigma.analysis;
12 12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Collection; 13import java.util.Collection;
17import java.util.Enumeration;
18import java.util.List; 14import java.util.List;
15import java.util.Map;
19import java.util.Set; 16import java.util.Set;
20import java.util.jar.JarEntry; 17import java.util.jar.JarEntry;
21import java.util.jar.JarFile; 18import java.util.jar.JarFile;
22import java.util.zip.ZipEntry;
23import java.util.zip.ZipInputStream;
24 19
25import javassist.ByteArrayClassPath;
26import javassist.CannotCompileException; 20import javassist.CannotCompileException;
27import javassist.ClassPool;
28import javassist.CtBehavior; 21import javassist.CtBehavior;
29import javassist.CtClass; 22import javassist.CtClass;
30import javassist.CtConstructor; 23import javassist.CtConstructor;
31import javassist.CtMethod; 24import javassist.CtMethod;
32import javassist.NotFoundException; 25import javassist.bytecode.AccessFlag;
33import javassist.bytecode.Descriptor; 26import javassist.bytecode.Descriptor;
27import javassist.bytecode.FieldInfo;
34import javassist.expr.ConstructorCall; 28import javassist.expr.ConstructorCall;
35import javassist.expr.ExprEditor; 29import javassist.expr.ExprEditor;
36import javassist.expr.FieldAccess; 30import javassist.expr.FieldAccess;
@@ -39,10 +33,10 @@ import javassist.expr.NewExpr;
39 33
40import com.google.common.collect.HashMultimap; 34import com.google.common.collect.HashMultimap;
41import com.google.common.collect.Lists; 35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
42import com.google.common.collect.Multimap; 37import com.google.common.collect.Multimap;
43import com.google.common.collect.Sets; 38import com.google.common.collect.Sets;
44 39
45import cuchaz.enigma.Constants;
46import cuchaz.enigma.mapping.ClassEntry; 40import cuchaz.enigma.mapping.ClassEntry;
47import cuchaz.enigma.mapping.ConstructorEntry; 41import cuchaz.enigma.mapping.ConstructorEntry;
48import cuchaz.enigma.mapping.Entry; 42import cuchaz.enigma.mapping.Entry;
@@ -57,89 +51,52 @@ public class JarIndex
57 private Multimap<String,MethodEntry> m_methodImplementations; 51 private Multimap<String,MethodEntry> m_methodImplementations;
58 private Multimap<Entry,Entry> m_methodCalls; 52 private Multimap<Entry,Entry> m_methodCalls;
59 private Multimap<FieldEntry,Entry> m_fieldCalls; 53 private Multimap<FieldEntry,Entry> m_fieldCalls;
54 private Multimap<String,String> m_innerClasses;
55 private Map<String,String> m_outerClasses;
60 56
61 public JarIndex( JarFile jar ) 57 public JarIndex( )
62 { 58 {
63 m_obfClassNames = Sets.newHashSet(); 59 m_obfClassNames = Sets.newHashSet();
64 m_ancestries = new Ancestries(); 60 m_ancestries = new Ancestries();
65 m_methodImplementations = HashMultimap.create(); 61 m_methodImplementations = HashMultimap.create();
66 m_methodCalls = HashMultimap.create(); 62 m_methodCalls = HashMultimap.create();
67 m_fieldCalls = HashMultimap.create(); 63 m_fieldCalls = HashMultimap.create();
68 64 m_innerClasses = HashMultimap.create();
69 // read the class names 65 m_outerClasses = Maps.newHashMap();
70 Enumeration<JarEntry> enumeration = jar.entries(); 66 }
71 while( enumeration.hasMoreElements() ) 67
68 public void indexJar( JarFile jar )
69 {
70 // pass 1: read the class names
71 for( JarEntry entry : JarClassIterator.getClassEntries( jar ) )
72 { 72 {
73 JarEntry entry = enumeration.nextElement();
74
75 // filter out non-classes
76 if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) )
77 {
78 continue;
79 }
80
81 String className = entry.getName().substring( 0, entry.getName().length() - 6 ); 73 String className = entry.getName().substring( 0, entry.getName().length() - 6 );
82 m_obfClassNames.add( Descriptor.toJvmName( className ) ); 74 m_obfClassNames.add( Descriptor.toJvmName( className ) );
83 } 75 }
84 }
85
86 public void indexJar( InputStream in )
87 throws IOException
88 {
89 ClassPool classPool = new ClassPool();
90 76
91 ZipInputStream zin = new ZipInputStream( in ); 77 // pass 2: index the types, methods
92 ZipEntry entry; 78 for( CtClass c : JarClassIterator.classes( jar ) )
93 while( ( entry = zin.getNextEntry() ) != null )
94 { 79 {
95 // filter out non-classes 80 m_ancestries.addSuperclass( c.getName(), c.getClassFile().getSuperclass() );
96 if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) ) 81 for( CtBehavior behavior : c.getDeclaredBehaviors() )
97 { 82 {
98 continue; 83 indexBehavior( behavior );
99 } 84 }
100 85 }
101 // read the class into a buffer 86
102 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 87 // pass 2: index inner classes
103 byte[] buf = new byte[Constants.KiB]; 88 for( CtClass c : JarClassIterator.classes( jar ) )
104 int totalNumBytesRead = 0; 89 {
105 while( zin.available() > 0 ) 90 String outerClassName = isInnerClass( c );
106 { 91 if( outerClassName != null )
107 int numBytesRead = zin.read( buf );
108 if( numBytesRead < 0 )
109 {
110 break;
111 }
112 bos.write( buf, 0, numBytesRead );
113
114 // sanity checking
115 totalNumBytesRead += numBytesRead;
116 if( totalNumBytesRead > Constants.MiB )
117 {
118 throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" );
119 }
120 }
121
122 // determine the class name (ie chop off the ".class")
123 String className = Descriptor.toJavaName( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) );
124
125 // get a javassist handle for the class
126 classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) );
127 try
128 {
129 CtClass c = classPool.get( className );
130 m_ancestries.addSuperclass( c.getName(), c.getClassFile().getSuperclass() );
131 for( CtBehavior behavior : c.getDeclaredBehaviors() )
132 {
133 indexBehavior( behavior );
134 }
135 }
136 catch( NotFoundException ex )
137 { 92 {
138 throw new Error( "Unable to load class: " + className ); 93 String innerClassName = Descriptor.toJvmName( c.getName() );
94 m_innerClasses.put( outerClassName, innerClassName );
95 m_outerClasses.put( innerClassName, outerClassName );
139 } 96 }
140 } 97 }
141 } 98 }
142 99
143 private void indexBehavior( CtBehavior behavior ) 100 private void indexBehavior( CtBehavior behavior )
144 { 101 {
145 // get the method entry 102 // get the method entry
@@ -226,6 +183,78 @@ public class JarIndex
226 } 183 }
227 } 184 }
228 185
186 @SuppressWarnings( "unchecked" )
187 private String isInnerClass( CtClass c )
188 {
189 String innerClassName = Descriptor.toJvmName( c.getName() );
190
191 // first, is this an anonymous class?
192 // for anonymous classes:
193 // the outer class is always a synthetic field
194 // there's at least one constructor with the type of the synthetic field as an argument
195 // this constructor is called exactly once by the class of the synthetic field
196
197 for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() )
198 {
199 boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
200 if( !isSynthetic )
201 {
202 continue;
203 }
204
205 // skip non-class types
206 if( !field.getDescriptor().startsWith( "L" ) )
207 {
208 continue;
209 }
210
211 // get the outer class from the field type
212 String outerClassName = Descriptor.toJvmName( Descriptor.toClassName( field.getDescriptor() ) );
213
214 // look for a constructor where this type is the first parameter
215 CtConstructor targetConstructor = null;
216 for( CtConstructor constructor : c.getDeclaredConstructors() )
217 {
218 String signature = Descriptor.getParamDescriptor( constructor.getMethodInfo().getDescriptor() );
219 if( Descriptor.numOfParameters( signature ) < 1 )
220 {
221 continue;
222 }
223
224 // match the first parameter to the outer class
225 Descriptor.Iterator iter = new Descriptor.Iterator( signature );
226 int pos = iter.next();
227 if( iter.isParameter() && signature.charAt( pos ) == 'L' )
228 {
229 String argumentDesc = signature.substring( pos, signature.indexOf(';', pos) + 1 );
230 String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) );
231 if( argumentClassName.equals( outerClassName ) )
232 {
233 // is this constructor called exactly once?
234 ConstructorEntry constructorEntry = new ConstructorEntry(
235 new ClassEntry( innerClassName ),
236 constructor.getMethodInfo().getDescriptor()
237 );
238 if( this.getMethodCallers( constructorEntry ).size() == 1 )
239 {
240 targetConstructor = constructor;
241 break;
242 }
243 }
244 }
245 }
246 if( targetConstructor == null )
247 {
248 continue;
249 }
250
251 // yeah, this is an inner class
252 return outerClassName;
253 }
254
255 return null;
256 }
257
229 public Set<String> getObfClassNames( ) 258 public Set<String> getObfClassNames( )
230 { 259 {
231 return m_obfClassNames; 260 return m_obfClassNames;
@@ -304,4 +333,14 @@ public class JarIndex
304 { 333 {
305 return m_methodCalls.get( entry ); 334 return m_methodCalls.get( entry );
306 } 335 }
336
337 public Collection<String> getInnerClasses( String obfOuterClassName )
338 {
339 return m_innerClasses.get( obfOuterClassName );
340 }
341
342 public String getOuterClass( String obfInnerClassName )
343 {
344 return m_outerClasses.get( obfInnerClassName );
345 }
307} 346}