diff options
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 195 |
1 files changed, 155 insertions, 40 deletions
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 34e8986..7d68c35 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | ******************************************************************************/ | 10 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.analysis; | 11 | package cuchaz.enigma.analysis; |
| 12 | 12 | ||
| 13 | import java.lang.reflect.Modifier; | ||
| 13 | import java.util.AbstractMap; | 14 | import java.util.AbstractMap; |
| 14 | import java.util.Collection; | 15 | import java.util.Collection; |
| 15 | import java.util.Iterator; | 16 | import java.util.Iterator; |
| @@ -92,8 +93,8 @@ public class JarIndex | |||
| 92 | // pass 2: index inner classes and anonymous classes | 93 | // pass 2: index inner classes and anonymous classes |
| 93 | for( CtClass c : JarClassIterator.classes( jar ) ) | 94 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 94 | { | 95 | { |
| 95 | String outerClassName = isInnerClass( c ); | 96 | String outerClassName = findOuterClass( c ); |
| 96 | if( outerClassName != null ) | 97 | if( outerClassName != null )// /* TEMP */ && false ) |
| 97 | { | 98 | { |
| 98 | String innerClassName = Descriptor.toJvmName( c.getName() ); | 99 | String innerClassName = Descriptor.toJvmName( c.getName() ); |
| 99 | m_innerClasses.put( outerClassName, innerClassName ); | 100 | m_innerClasses.put( outerClassName, innerClassName ); |
| @@ -102,6 +103,14 @@ public class JarIndex | |||
| 102 | if( isAnonymousClass( c, outerClassName ) ) | 103 | if( isAnonymousClass( c, outerClassName ) ) |
| 103 | { | 104 | { |
| 104 | m_anonymousClasses.add( innerClassName ); | 105 | m_anonymousClasses.add( innerClassName ); |
| 106 | |||
| 107 | // DEBUG | ||
| 108 | System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); | ||
| 109 | } | ||
| 110 | else | ||
| 111 | { | ||
| 112 | // DEBUG | ||
| 113 | System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); | ||
| 105 | } | 114 | } |
| 106 | } | 115 | } |
| 107 | } | 116 | } |
| @@ -175,6 +184,10 @@ public class JarIndex | |||
| 175 | @Override | 184 | @Override |
| 176 | public void edit( ConstructorCall call ) | 185 | public void edit( ConstructorCall call ) |
| 177 | { | 186 | { |
| 187 | boolean isSuper = call.getMethodName().equals( "super" ); | ||
| 188 | // TODO: make method reference class, update method calls tree to use Invocation instances | ||
| 189 | // this might end up being a big refactor... =( | ||
| 190 | |||
| 178 | String className = Descriptor.toJvmName( call.getClassName() ); | 191 | String className = Descriptor.toJvmName( call.getClassName() ); |
| 179 | ConstructorEntry calledConstructorEntry = new ConstructorEntry( | 192 | ConstructorEntry calledConstructorEntry = new ConstructorEntry( |
| 180 | new ClassEntry( className ), | 193 | new ClassEntry( className ), |
| @@ -201,75 +214,168 @@ public class JarIndex | |||
| 201 | } | 214 | } |
| 202 | } | 215 | } |
| 203 | 216 | ||
| 204 | @SuppressWarnings( "unchecked" ) | 217 | private String findOuterClass( CtClass c ) |
| 205 | private String isInnerClass( CtClass c ) | ||
| 206 | { | 218 | { |
| 207 | // inner classes: | 219 | // inner classes: |
| 208 | // the outer class is always a synthetic field | 220 | // have constructors that can (illegally) set synthetic fields |
| 209 | // there's at least one constructor with the type of the synthetic field as an argument | 221 | // the outer class is the only class that calls constructors |
| 210 | 222 | ||
| 211 | for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() ) | 223 | // use the synthetic fields to find the synthetic constructors |
| 224 | for( CtConstructor constructor : c.getDeclaredConstructors() ) | ||
| 212 | { | 225 | { |
| 213 | boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; | 226 | if( !isIllegalConstructor( constructor ) ) |
| 214 | if( !isSynthetic ) | ||
| 215 | { | 227 | { |
| 216 | continue; | 228 | continue; |
| 217 | } | 229 | } |
| 218 | 230 | ||
| 219 | // skip non-class types | 231 | // who calls this constructor? |
| 220 | if( !field.getDescriptor().startsWith( "L" ) ) | 232 | Set<ClassEntry> callerClasses = Sets.newHashSet(); |
| 233 | ConstructorEntry constructorEntry = new ConstructorEntry( | ||
| 234 | new ClassEntry( Descriptor.toJvmName( c.getName() ) ), | ||
| 235 | constructor.getMethodInfo().getDescriptor() | ||
| 236 | ); | ||
| 237 | for( Entry callerEntry : getMethodCallers( constructorEntry ) ) | ||
| 221 | { | 238 | { |
| 222 | continue; | 239 | callerClasses.add( callerEntry.getClassEntry() ); |
| 223 | } | 240 | } |
| 224 | 241 | ||
| 225 | // get the outer class from the field type | 242 | // is this called by exactly one class? |
| 226 | String outerClassName = Descriptor.toJvmName( Descriptor.toClassName( field.getDescriptor() ) ); | 243 | if( callerClasses.size() == 1 ) |
| 227 | 244 | { | |
| 228 | // look for a constructor where this type is the first parameter | 245 | return callerClasses.iterator().next().getName(); |
| 229 | CtConstructor targetConstructor = null; | 246 | } |
| 230 | for( CtConstructor constructor : c.getDeclaredConstructors() ) | 247 | else if( callerClasses.size() > 1 ) |
| 248 | { | ||
| 249 | // TEMP | ||
| 250 | System.out.println( "WARNING: Illegal class called by more than one class!" + callerClasses ); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | return null; | ||
| 255 | } | ||
| 256 | |||
| 257 | @SuppressWarnings( "unchecked" ) | ||
| 258 | private boolean isIllegalConstructor( CtConstructor constructor ) | ||
| 259 | { | ||
| 260 | // illegal constructors only set synthetic member fields, then call super() | ||
| 261 | String className = constructor.getDeclaringClass().getName(); | ||
| 262 | |||
| 263 | // collect all the field accesses, constructor calls, and method calls | ||
| 264 | final List<FieldAccess> illegalFieldWrites = Lists.newArrayList(); | ||
| 265 | final List<ConstructorCall> constructorCalls = Lists.newArrayList(); | ||
| 266 | final List<MethodCall> methodCalls = Lists.newArrayList(); | ||
| 267 | try | ||
| 268 | { | ||
| 269 | constructor.instrument( new ExprEditor( ) | ||
| 231 | { | 270 | { |
| 232 | String signature = Descriptor.getParamDescriptor( constructor.getMethodInfo().getDescriptor() ); | 271 | @Override |
| 233 | if( Descriptor.numOfParameters( signature ) < 1 ) | 272 | public void edit( FieldAccess fieldAccess ) |
| 234 | { | 273 | { |
| 235 | continue; | 274 | if( fieldAccess.isWriter() && constructorCalls.isEmpty() ) |
| 275 | { | ||
| 276 | illegalFieldWrites.add( fieldAccess ); | ||
| 277 | } | ||
| 236 | } | 278 | } |
| 237 | 279 | ||
| 238 | // match the first parameter to the outer class | 280 | @Override |
| 239 | Descriptor.Iterator iter = new Descriptor.Iterator( signature ); | 281 | public void edit( ConstructorCall constructorCall ) |
| 240 | int pos = iter.next(); | ||
| 241 | if( iter.isParameter() && signature.charAt( pos ) == 'L' ) | ||
| 242 | { | 282 | { |
| 243 | String argumentDesc = signature.substring( pos, signature.indexOf(';', pos) + 1 ); | 283 | constructorCalls.add( constructorCall ); |
| 244 | String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); | 284 | } |
| 245 | if( argumentClassName.equals( outerClassName ) ) | 285 | |
| 246 | { | 286 | @Override |
| 247 | targetConstructor = constructor; | 287 | public void edit( MethodCall methodCall ) |
| 248 | break; | 288 | { |
| 249 | } | 289 | methodCalls.add( methodCall ); |
| 250 | } | 290 | } |
| 291 | } ); | ||
| 292 | } | ||
| 293 | catch( CannotCompileException ex ) | ||
| 294 | { | ||
| 295 | // we're not compiling anything... this is stupid | ||
| 296 | throw new Error( ex ); | ||
| 297 | } | ||
| 298 | |||
| 299 | // method calls are not allowed | ||
| 300 | if( !methodCalls.isEmpty() ) | ||
| 301 | { | ||
| 302 | return false; | ||
| 303 | } | ||
| 304 | |||
| 305 | // is there only one constructor call? | ||
| 306 | if( constructorCalls.size() != 1 ) | ||
| 307 | { | ||
| 308 | return false; | ||
| 309 | } | ||
| 310 | |||
| 311 | // is the call to super? | ||
| 312 | ConstructorCall constructorCall = constructorCalls.get( 0 ); | ||
| 313 | if( !constructorCall.getMethodName().equals( "super" ) ) | ||
| 314 | { | ||
| 315 | return false; | ||
| 316 | } | ||
| 317 | |||
| 318 | // are there any illegal field writes? | ||
| 319 | if( illegalFieldWrites.isEmpty() ) | ||
| 320 | { | ||
| 321 | return false; | ||
| 322 | } | ||
| 323 | |||
| 324 | // are all the writes to synthetic fields? | ||
| 325 | for( FieldAccess fieldWrite : illegalFieldWrites ) | ||
| 326 | { | ||
| 327 | // all illegal writes have to be to the local class | ||
| 328 | if( !fieldWrite.getClassName().equals( className ) ) | ||
| 329 | { | ||
| 330 | System.err.println( String.format( "WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName() ) ); | ||
| 331 | return false; | ||
| 251 | } | 332 | } |
| 252 | if( targetConstructor == null ) | 333 | |
| 334 | // find the field | ||
| 335 | FieldInfo fieldInfo = null; | ||
| 336 | for( FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields() ) | ||
| 253 | { | 337 | { |
| 254 | continue; | 338 | if( info.getName().equals( fieldWrite.getFieldName() ) ) |
| 339 | { | ||
| 340 | fieldInfo = info; | ||
| 341 | break; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | if( fieldInfo == null ) | ||
| 345 | { | ||
| 346 | // field is in a superclass or something, can't be a local synthetic member | ||
| 347 | return false; | ||
| 255 | } | 348 | } |
| 256 | 349 | ||
| 257 | // yeah, this is an inner class | 350 | // is this field synthetic? |
| 258 | return outerClassName; | 351 | boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; |
| 352 | if( !isSynthetic ) | ||
| 353 | { | ||
| 354 | System.err.println( String.format( "WARNING: illegal write to non synthetic field %s.%s", className, fieldInfo.getName() ) ); | ||
| 355 | return false; | ||
| 356 | } | ||
| 259 | } | 357 | } |
| 260 | 358 | ||
| 261 | return null; | 359 | // we passed all the tests! |
| 360 | return true; | ||
| 262 | } | 361 | } |
| 263 | 362 | ||
| 264 | private boolean isAnonymousClass( CtClass c, String outerClassName ) | 363 | private boolean isAnonymousClass( CtClass c, String outerClassName ) |
| 265 | { | 364 | { |
| 266 | String innerClassName = Descriptor.toJvmName( c.getName() ); | 365 | String innerClassName = Descriptor.toJvmName( c.getName() ); |
| 267 | 366 | ||
| 268 | // anonymous classes: | 367 | // anonymous classes: |
| 368 | // can't be abstract | ||
| 269 | // have only one constructor | 369 | // have only one constructor |
| 270 | // it's called exactly once by the outer class | 370 | // it's called exactly once by the outer class |
| 271 | // type of inner class not referenced anywhere in outer class | 371 | // type of inner class not referenced anywhere in outer class |
| 272 | 372 | ||
| 373 | // is absract? | ||
| 374 | if( Modifier.isAbstract( c.getModifiers() ) ) | ||
| 375 | { | ||
| 376 | return false; | ||
| 377 | } | ||
| 378 | |||
| 273 | // is there exactly one constructor? | 379 | // is there exactly one constructor? |
| 274 | if( c.getDeclaredConstructors().length != 1 ) | 380 | if( c.getDeclaredConstructors().length != 1 ) |
| 275 | { | 381 | { |
| @@ -282,7 +388,16 @@ public class JarIndex | |||
| 282 | new ClassEntry( innerClassName ), | 388 | new ClassEntry( innerClassName ), |
| 283 | constructor.getMethodInfo().getDescriptor() | 389 | constructor.getMethodInfo().getDescriptor() |
| 284 | ); | 390 | ); |
| 285 | return getMethodCallers( constructorEntry ).size() == 1; | 391 | if( getMethodCallers( constructorEntry ).size() != 1 ) |
| 392 | { | ||
| 393 | return false; | ||
| 394 | } | ||
| 395 | |||
| 396 | // TODO: check outer class doesn't reference type | ||
| 397 | // except this is hard because we can't just load the outer class now | ||
| 398 | // we'd have to pre-index those references in the JarIndex | ||
| 399 | |||
| 400 | return true; | ||
| 286 | } | 401 | } |
| 287 | 402 | ||
| 288 | public Set<String> getObfClassNames( ) | 403 | public Set<String> getObfClassNames( ) |