diff options
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 155 |
1 files changed, 138 insertions, 17 deletions
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 9962bfa..34e8986 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -10,7 +10,9 @@ | |||
| 10 | ******************************************************************************/ | 10 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.analysis; | 11 | package cuchaz.enigma.analysis; |
| 12 | 12 | ||
| 13 | import java.util.AbstractMap; | ||
| 13 | import java.util.Collection; | 14 | import java.util.Collection; |
| 15 | import java.util.Iterator; | ||
| 14 | import java.util.List; | 16 | import java.util.List; |
| 15 | import java.util.Map; | 17 | import java.util.Map; |
| 16 | import java.util.Set; | 18 | import java.util.Set; |
| @@ -37,6 +39,7 @@ import com.google.common.collect.Maps; | |||
| 37 | import com.google.common.collect.Multimap; | 39 | import com.google.common.collect.Multimap; |
| 38 | import com.google.common.collect.Sets; | 40 | import com.google.common.collect.Sets; |
| 39 | 41 | ||
| 42 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 40 | import cuchaz.enigma.mapping.ClassEntry; | 43 | import cuchaz.enigma.mapping.ClassEntry; |
| 41 | import cuchaz.enigma.mapping.ConstructorEntry; | 44 | import cuchaz.enigma.mapping.ConstructorEntry; |
| 42 | import cuchaz.enigma.mapping.Entry; | 45 | import cuchaz.enigma.mapping.Entry; |
| @@ -53,6 +56,7 @@ public class JarIndex | |||
| 53 | private Multimap<FieldEntry,Entry> m_fieldCalls; | 56 | private Multimap<FieldEntry,Entry> m_fieldCalls; |
| 54 | private Multimap<String,String> m_innerClasses; | 57 | private Multimap<String,String> m_innerClasses; |
| 55 | private Map<String,String> m_outerClasses; | 58 | private Map<String,String> m_outerClasses; |
| 59 | private Set<String> m_anonymousClasses; | ||
| 56 | 60 | ||
| 57 | public JarIndex( ) | 61 | public JarIndex( ) |
| 58 | { | 62 | { |
| @@ -63,6 +67,7 @@ public class JarIndex | |||
| 63 | m_fieldCalls = HashMultimap.create(); | 67 | m_fieldCalls = HashMultimap.create(); |
| 64 | m_innerClasses = HashMultimap.create(); | 68 | m_innerClasses = HashMultimap.create(); |
| 65 | m_outerClasses = Maps.newHashMap(); | 69 | m_outerClasses = Maps.newHashMap(); |
| 70 | m_anonymousClasses = Sets.newHashSet(); | ||
| 66 | } | 71 | } |
| 67 | 72 | ||
| 68 | public void indexJar( JarFile jar ) | 73 | public void indexJar( JarFile jar ) |
| @@ -84,7 +89,7 @@ public class JarIndex | |||
| 84 | } | 89 | } |
| 85 | } | 90 | } |
| 86 | 91 | ||
| 87 | // pass 2: index inner classes | 92 | // pass 2: index inner classes and anonymous classes |
| 88 | for( CtClass c : JarClassIterator.classes( jar ) ) | 93 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 89 | { | 94 | { |
| 90 | String outerClassName = isInnerClass( c ); | 95 | String outerClassName = isInnerClass( c ); |
| @@ -93,8 +98,21 @@ public class JarIndex | |||
| 93 | String innerClassName = Descriptor.toJvmName( c.getName() ); | 98 | String innerClassName = Descriptor.toJvmName( c.getName() ); |
| 94 | m_innerClasses.put( outerClassName, innerClassName ); | 99 | m_innerClasses.put( outerClassName, innerClassName ); |
| 95 | m_outerClasses.put( innerClassName, outerClassName ); | 100 | m_outerClasses.put( innerClassName, outerClassName ); |
| 101 | |||
| 102 | if( isAnonymousClass( c, outerClassName ) ) | ||
| 103 | { | ||
| 104 | m_anonymousClasses.add( innerClassName ); | ||
| 105 | } | ||
| 96 | } | 106 | } |
| 97 | } | 107 | } |
| 108 | |||
| 109 | // step 3: update other indicies with inner class info | ||
| 110 | Map<String,String> renames = Maps.newHashMap(); | ||
| 111 | for( Map.Entry<String,String> entry : m_outerClasses.entrySet() ) | ||
| 112 | { | ||
| 113 | renames.put( entry.getKey(), entry.getValue() + "$" + entry.getKey() ); | ||
| 114 | } | ||
| 115 | renameClasses( renames ); | ||
| 98 | } | 116 | } |
| 99 | 117 | ||
| 100 | private void indexBehavior( CtBehavior behavior ) | 118 | private void indexBehavior( CtBehavior behavior ) |
| @@ -186,14 +204,10 @@ public class JarIndex | |||
| 186 | @SuppressWarnings( "unchecked" ) | 204 | @SuppressWarnings( "unchecked" ) |
| 187 | private String isInnerClass( CtClass c ) | 205 | private String isInnerClass( CtClass c ) |
| 188 | { | 206 | { |
| 189 | String innerClassName = Descriptor.toJvmName( c.getName() ); | 207 | // inner classes: |
| 190 | |||
| 191 | // first, is this an anonymous class? | ||
| 192 | // for anonymous classes: | ||
| 193 | // the outer class is always a synthetic field | 208 | // 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 | 209 | // 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 | 210 | |
| 196 | |||
| 197 | for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() ) | 211 | for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() ) |
| 198 | { | 212 | { |
| 199 | boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; | 213 | boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; |
| @@ -230,16 +244,8 @@ public class JarIndex | |||
| 230 | String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); | 244 | String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); |
| 231 | if( argumentClassName.equals( outerClassName ) ) | 245 | if( argumentClassName.equals( outerClassName ) ) |
| 232 | { | 246 | { |
| 233 | // is this constructor called exactly once? | 247 | targetConstructor = constructor; |
| 234 | ConstructorEntry constructorEntry = new ConstructorEntry( | 248 | break; |
| 235 | new ClassEntry( innerClassName ), | ||
| 236 | constructor.getMethodInfo().getDescriptor() | ||
| 237 | ); | ||
| 238 | if( this.getMethodCallers( constructorEntry ).size() == 1 ) | ||
| 239 | { | ||
| 240 | targetConstructor = constructor; | ||
| 241 | break; | ||
| 242 | } | ||
| 243 | } | 249 | } |
| 244 | } | 250 | } |
| 245 | } | 251 | } |
| @@ -255,6 +261,30 @@ public class JarIndex | |||
| 255 | return null; | 261 | return null; |
| 256 | } | 262 | } |
| 257 | 263 | ||
| 264 | private boolean isAnonymousClass( CtClass c, String outerClassName ) | ||
| 265 | { | ||
| 266 | String innerClassName = Descriptor.toJvmName( c.getName() ); | ||
| 267 | |||
| 268 | // anonymous classes: | ||
| 269 | // have only one constructor | ||
| 270 | // it's called exactly once by the outer class | ||
| 271 | // type of inner class not referenced anywhere in outer class | ||
| 272 | |||
| 273 | // is there exactly one constructor? | ||
| 274 | if( c.getDeclaredConstructors().length != 1 ) | ||
| 275 | { | ||
| 276 | return false; | ||
| 277 | } | ||
| 278 | CtConstructor constructor = c.getDeclaredConstructors()[0]; | ||
| 279 | |||
| 280 | // is this constructor called exactly once? | ||
| 281 | ConstructorEntry constructorEntry = new ConstructorEntry( | ||
| 282 | new ClassEntry( innerClassName ), | ||
| 283 | constructor.getMethodInfo().getDescriptor() | ||
| 284 | ); | ||
| 285 | return getMethodCallers( constructorEntry ).size() == 1; | ||
| 286 | } | ||
| 287 | |||
| 258 | public Set<String> getObfClassNames( ) | 288 | public Set<String> getObfClassNames( ) |
| 259 | { | 289 | { |
| 260 | return m_obfClassNames; | 290 | return m_obfClassNames; |
| @@ -343,4 +373,95 @@ public class JarIndex | |||
| 343 | { | 373 | { |
| 344 | return m_outerClasses.get( obfInnerClassName ); | 374 | return m_outerClasses.get( obfInnerClassName ); |
| 345 | } | 375 | } |
| 376 | |||
| 377 | public boolean isAnonymousClass( String obfInnerClassName ) | ||
| 378 | { | ||
| 379 | return m_anonymousClasses.contains( obfInnerClassName ); | ||
| 380 | } | ||
| 381 | |||
| 382 | private void renameClasses( Map<String,String> renames ) | ||
| 383 | { | ||
| 384 | m_ancestries.renameClasses( renames ); | ||
| 385 | renameMultimap( renames, m_methodImplementations ); | ||
| 386 | renameMultimap( renames, m_methodCalls ); | ||
| 387 | renameMultimap( renames, m_fieldCalls ); | ||
| 388 | } | ||
| 389 | |||
| 390 | private <T,U> void renameMultimap( Map<String,String> renames, Multimap<T,U> map ) | ||
| 391 | { | ||
| 392 | // for each key/value pair... | ||
| 393 | Set<Map.Entry<T,U>> entriesToAdd = Sets.newHashSet(); | ||
| 394 | Iterator<Map.Entry<T,U>> iter = map.entries().iterator(); | ||
| 395 | while( iter.hasNext() ) | ||
| 396 | { | ||
| 397 | Map.Entry<T,U> entry = iter.next(); | ||
| 398 | iter.remove(); | ||
| 399 | entriesToAdd.add( new AbstractMap.SimpleEntry<T,U>( | ||
| 400 | renameEntry( renames, entry.getKey() ), | ||
| 401 | renameEntry( renames, entry.getValue() ) | ||
| 402 | ) ); | ||
| 403 | } | ||
| 404 | for( Map.Entry<T,U> entry : entriesToAdd ) | ||
| 405 | { | ||
| 406 | map.put( entry.getKey(), entry.getValue() ); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | @SuppressWarnings( "unchecked" ) | ||
| 411 | private <T> T renameEntry( Map<String,String> renames, T entry ) | ||
| 412 | { | ||
| 413 | if( entry instanceof String ) | ||
| 414 | { | ||
| 415 | String stringEntry = (String)entry; | ||
| 416 | if( renames.containsKey( stringEntry ) ) | ||
| 417 | { | ||
| 418 | return (T)renames.get( stringEntry ); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | else if( entry instanceof ClassEntry ) | ||
| 422 | { | ||
| 423 | ClassEntry classEntry = (ClassEntry)entry; | ||
| 424 | return (T)new ClassEntry( renameEntry( renames, classEntry.getClassName() ) ); | ||
| 425 | } | ||
| 426 | else if( entry instanceof FieldEntry ) | ||
| 427 | { | ||
| 428 | FieldEntry fieldEntry = (FieldEntry)entry; | ||
| 429 | return (T)new FieldEntry( | ||
| 430 | renameEntry( renames, fieldEntry.getClassEntry() ), | ||
| 431 | fieldEntry.getName() | ||
| 432 | ); | ||
| 433 | } | ||
| 434 | else if( entry instanceof ConstructorEntry ) | ||
| 435 | { | ||
| 436 | ConstructorEntry constructorEntry = (ConstructorEntry)entry; | ||
| 437 | return (T)new ConstructorEntry( | ||
| 438 | renameEntry( renames, constructorEntry.getClassEntry() ), | ||
| 439 | constructorEntry.getSignature() | ||
| 440 | ); | ||
| 441 | } | ||
| 442 | else if( entry instanceof MethodEntry ) | ||
| 443 | { | ||
| 444 | MethodEntry methodEntry = (MethodEntry)entry; | ||
| 445 | return (T)new MethodEntry( | ||
| 446 | renameEntry( renames, methodEntry.getClassEntry() ), | ||
| 447 | methodEntry.getName(), | ||
| 448 | methodEntry.getSignature() | ||
| 449 | ); | ||
| 450 | } | ||
| 451 | else if( entry instanceof ArgumentEntry ) | ||
| 452 | { | ||
| 453 | ArgumentEntry argumentEntry = (ArgumentEntry)entry; | ||
| 454 | return (T)new ArgumentEntry( | ||
| 455 | renameEntry( renames, argumentEntry.getMethodEntry() ), | ||
| 456 | argumentEntry.getIndex(), | ||
| 457 | argumentEntry.getName() | ||
| 458 | ); | ||
| 459 | } | ||
| 460 | else | ||
| 461 | { | ||
| 462 | throw new Error( "Not an entry: " + entry ); | ||
| 463 | } | ||
| 464 | |||
| 465 | return entry; | ||
| 466 | } | ||
| 346 | } | 467 | } |