summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/analysis/JarIndex.java
diff options
context:
space:
mode:
authorGravatar jeff2014-08-18 00:55:30 -0400
committerGravatar jeff2014-08-18 00:55:30 -0400
commit34c1e8e64ec4575527a19fb4cb0640c57da784db (patch)
tree44e3f1d50f8d8b8a9ab7c26dd94b58cba750cc67 /src/cuchaz/enigma/analysis/JarIndex.java
parentadded support for automatic reconstruction of inner and anonymous classes (diff)
downloadenigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.tar.gz
enigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.tar.xz
enigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.zip
crap-ton of bug fixes for inner classes
Diffstat (limited to 'src/cuchaz/enigma/analysis/JarIndex.java')
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java195
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 ******************************************************************************/
11package cuchaz.enigma.analysis; 11package cuchaz.enigma.analysis;
12 12
13import java.lang.reflect.Modifier;
13import java.util.AbstractMap; 14import java.util.AbstractMap;
14import java.util.Collection; 15import java.util.Collection;
15import java.util.Iterator; 16import 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( )