diff options
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 187 |
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 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.analysis; | 11 | package cuchaz.enigma.analysis; |
| 12 | 12 | ||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Collection; | 13 | import java.util.Collection; |
| 17 | import java.util.Enumeration; | ||
| 18 | import java.util.List; | 14 | import java.util.List; |
| 15 | import java.util.Map; | ||
| 19 | import java.util.Set; | 16 | import java.util.Set; |
| 20 | import java.util.jar.JarEntry; | 17 | import java.util.jar.JarEntry; |
| 21 | import java.util.jar.JarFile; | 18 | import java.util.jar.JarFile; |
| 22 | import java.util.zip.ZipEntry; | ||
| 23 | import java.util.zip.ZipInputStream; | ||
| 24 | 19 | ||
| 25 | import javassist.ByteArrayClassPath; | ||
| 26 | import javassist.CannotCompileException; | 20 | import javassist.CannotCompileException; |
| 27 | import javassist.ClassPool; | ||
| 28 | import javassist.CtBehavior; | 21 | import javassist.CtBehavior; |
| 29 | import javassist.CtClass; | 22 | import javassist.CtClass; |
| 30 | import javassist.CtConstructor; | 23 | import javassist.CtConstructor; |
| 31 | import javassist.CtMethod; | 24 | import javassist.CtMethod; |
| 32 | import javassist.NotFoundException; | 25 | import javassist.bytecode.AccessFlag; |
| 33 | import javassist.bytecode.Descriptor; | 26 | import javassist.bytecode.Descriptor; |
| 27 | import javassist.bytecode.FieldInfo; | ||
| 34 | import javassist.expr.ConstructorCall; | 28 | import javassist.expr.ConstructorCall; |
| 35 | import javassist.expr.ExprEditor; | 29 | import javassist.expr.ExprEditor; |
| 36 | import javassist.expr.FieldAccess; | 30 | import javassist.expr.FieldAccess; |
| @@ -39,10 +33,10 @@ import javassist.expr.NewExpr; | |||
| 39 | 33 | ||
| 40 | import com.google.common.collect.HashMultimap; | 34 | import com.google.common.collect.HashMultimap; |
| 41 | import com.google.common.collect.Lists; | 35 | import com.google.common.collect.Lists; |
| 36 | import com.google.common.collect.Maps; | ||
| 42 | import com.google.common.collect.Multimap; | 37 | import com.google.common.collect.Multimap; |
| 43 | import com.google.common.collect.Sets; | 38 | import com.google.common.collect.Sets; |
| 44 | 39 | ||
| 45 | import cuchaz.enigma.Constants; | ||
| 46 | import cuchaz.enigma.mapping.ClassEntry; | 40 | import cuchaz.enigma.mapping.ClassEntry; |
| 47 | import cuchaz.enigma.mapping.ConstructorEntry; | 41 | import cuchaz.enigma.mapping.ConstructorEntry; |
| 48 | import cuchaz.enigma.mapping.Entry; | 42 | import 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 | } |