diff options
| author | 2014-08-30 11:41:17 -0400 | |
|---|---|---|
| committer | 2014-08-30 11:41:17 -0400 | |
| commit | e43fac9f55cfeebacd869352bfb090b7d8d063c1 (patch) | |
| tree | dd4b01daa04dbdcecc765c7270e18bdf1b63d97f /src/cuchaz/enigma/convert/ClassIdentity.java | |
| parent | working on version conversion (diff) | |
| download | enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.tar.gz enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.tar.xz enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.zip | |
got a decent class matcher working
Diffstat (limited to '')
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassIdentity.java | 255 |
1 files changed, 226 insertions, 29 deletions
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index aecf7fc..0a3a449 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java | |||
| @@ -13,10 +13,12 @@ package cuchaz.enigma.convert; | |||
| 13 | import java.io.UnsupportedEncodingException; | 13 | import java.io.UnsupportedEncodingException; |
| 14 | import java.security.MessageDigest; | 14 | import java.security.MessageDigest; |
| 15 | import java.security.NoSuchAlgorithmException; | 15 | import java.security.NoSuchAlgorithmException; |
| 16 | import java.util.Enumeration; | ||
| 16 | import java.util.List; | 17 | import java.util.List; |
| 17 | import java.util.Map; | 18 | import java.util.Map; |
| 18 | import java.util.Set; | 19 | import java.util.Set; |
| 19 | 20 | ||
| 21 | import javassist.CannotCompileException; | ||
| 20 | import javassist.CtBehavior; | 22 | import javassist.CtBehavior; |
| 21 | import javassist.CtClass; | 23 | import javassist.CtClass; |
| 22 | import javassist.CtConstructor; | 24 | import javassist.CtConstructor; |
| @@ -27,34 +29,58 @@ import javassist.bytecode.CodeIterator; | |||
| 27 | import javassist.bytecode.ConstPool; | 29 | import javassist.bytecode.ConstPool; |
| 28 | import javassist.bytecode.Descriptor; | 30 | import javassist.bytecode.Descriptor; |
| 29 | import javassist.bytecode.Opcode; | 31 | import javassist.bytecode.Opcode; |
| 32 | import javassist.expr.ConstructorCall; | ||
| 33 | import javassist.expr.ExprEditor; | ||
| 34 | import javassist.expr.FieldAccess; | ||
| 35 | import javassist.expr.MethodCall; | ||
| 36 | import javassist.expr.NewExpr; | ||
| 30 | 37 | ||
| 31 | import com.beust.jcommander.internal.Maps; | 38 | import com.beust.jcommander.internal.Maps; |
| 32 | import com.beust.jcommander.internal.Sets; | 39 | import com.beust.jcommander.internal.Sets; |
| 33 | import com.google.common.collect.Lists; | 40 | import com.google.common.collect.Lists; |
| 34 | 41 | ||
| 42 | import cuchaz.enigma.Constants; | ||
| 35 | import cuchaz.enigma.Util; | 43 | import cuchaz.enigma.Util; |
| 44 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 45 | import cuchaz.enigma.analysis.EntryReference; | ||
| 46 | import cuchaz.enigma.analysis.JarIndex; | ||
| 36 | import cuchaz.enigma.bytecode.ConstPoolEditor; | 47 | import cuchaz.enigma.bytecode.ConstPoolEditor; |
| 37 | import cuchaz.enigma.bytecode.InfoType; | 48 | import cuchaz.enigma.bytecode.InfoType; |
| 38 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | 49 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; |
| 50 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 51 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | 52 | import cuchaz.enigma.mapping.ClassEntry; |
| 53 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 54 | import cuchaz.enigma.mapping.Entry; | ||
| 55 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 56 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 40 | import cuchaz.enigma.mapping.SignatureUpdater; | 57 | import cuchaz.enigma.mapping.SignatureUpdater; |
| 41 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; | 58 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; |
| 42 | 59 | ||
| 43 | public class ClassIdentity | 60 | public class ClassIdentity |
| 44 | { | 61 | { |
| 45 | private ClassEntry m_classEntry; | 62 | private ClassEntry m_classEntry; |
| 63 | private SidedClassNamer m_namer; | ||
| 46 | private Set<String> m_fields; | 64 | private Set<String> m_fields; |
| 47 | private Set<String> m_methods; | 65 | private Set<String> m_methods; |
| 48 | private Set<String> m_constructors; | 66 | private Set<String> m_constructors; |
| 49 | private String m_staticInitializer; | 67 | private String m_staticInitializer; |
| 68 | private String m_extends; | ||
| 69 | private Set<String> m_implements; | ||
| 70 | private Set<String> m_implementations; | ||
| 71 | private Set<String> m_references; | ||
| 50 | 72 | ||
| 51 | public ClassIdentity( CtClass c ) | 73 | public ClassIdentity( CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences ) |
| 52 | { | 74 | { |
| 75 | m_namer = namer; | ||
| 76 | |||
| 77 | // stuff from the bytecode | ||
| 78 | |||
| 53 | m_classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); | 79 | m_classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); |
| 54 | m_fields = Sets.newHashSet(); | 80 | m_fields = Sets.newHashSet(); |
| 55 | for( CtField field : c.getDeclaredFields() ) | 81 | for( CtField field : c.getDeclaredFields() ) |
| 56 | { | 82 | { |
| 57 | m_fields.add( scrubSignature( scrubSignature( field.getSignature() ) ) ); | 83 | m_fields.add( scrubSignature( field.getSignature() ) ); |
| 58 | } | 84 | } |
| 59 | m_methods = Sets.newHashSet(); | 85 | m_methods = Sets.newHashSet(); |
| 60 | for( CtMethod method : c.getDeclaredMethods() ) | 86 | for( CtMethod method : c.getDeclaredMethods() ) |
| @@ -71,6 +97,73 @@ public class ClassIdentity | |||
| 71 | { | 97 | { |
| 72 | m_staticInitializer = getBehaviorSignature( c.getClassInitializer() ); | 98 | m_staticInitializer = getBehaviorSignature( c.getClassInitializer() ); |
| 73 | } | 99 | } |
| 100 | m_extends = ""; | ||
| 101 | if( c.getClassFile().getSuperclass() != null ) | ||
| 102 | { | ||
| 103 | m_extends = scrubClassName( c.getClassFile().getSuperclass() ); | ||
| 104 | } | ||
| 105 | m_implements = Sets.newHashSet(); | ||
| 106 | for( String interfaceName : c.getClassFile().getInterfaces() ) | ||
| 107 | { | ||
| 108 | m_implements.add( scrubClassName( interfaceName ) ); | ||
| 109 | } | ||
| 110 | |||
| 111 | // stuff from the jar index | ||
| 112 | |||
| 113 | m_implementations = Sets.newHashSet(); | ||
| 114 | @SuppressWarnings( "unchecked" ) | ||
| 115 | Enumeration<ClassImplementationsTreeNode> implementations = index.getClassImplementations( null, m_classEntry ).children(); | ||
| 116 | while( implementations.hasMoreElements() ) | ||
| 117 | { | ||
| 118 | ClassImplementationsTreeNode node = implementations.nextElement(); | ||
| 119 | m_implementations.add( scrubClassName( node.getClassEntry().getName() ) ); | ||
| 120 | } | ||
| 121 | |||
| 122 | m_references = Sets.newHashSet(); | ||
| 123 | if( useReferences ) | ||
| 124 | { | ||
| 125 | for( CtField field : c.getDeclaredFields() ) | ||
| 126 | { | ||
| 127 | FieldEntry fieldEntry = new FieldEntry( m_classEntry, field.getName() ); | ||
| 128 | for( EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences( fieldEntry ) ) | ||
| 129 | { | ||
| 130 | addReference( reference ); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | for( CtMethod method : c.getDeclaredMethods() ) | ||
| 134 | { | ||
| 135 | MethodEntry methodEntry = new MethodEntry( m_classEntry, method.getName(), method.getSignature() ); | ||
| 136 | for( EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences( methodEntry ) ) | ||
| 137 | { | ||
| 138 | addReference( reference ); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | for( CtConstructor constructor : c.getDeclaredConstructors() ) | ||
| 142 | { | ||
| 143 | ConstructorEntry constructorEntry = new ConstructorEntry( m_classEntry, constructor.getSignature() ); | ||
| 144 | for( EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences( constructorEntry ) ) | ||
| 145 | { | ||
| 146 | addReference( reference ); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | private void addReference( EntryReference<? extends Entry,BehaviorEntry> reference ) | ||
| 153 | { | ||
| 154 | if( reference.context.getSignature() != null ) | ||
| 155 | { | ||
| 156 | m_references.add( String.format( "%s_%s", | ||
| 157 | scrubClassName( reference.context.getClassName() ), | ||
| 158 | scrubSignature( reference.context.getSignature() ) | ||
| 159 | ) ); | ||
| 160 | } | ||
| 161 | else | ||
| 162 | { | ||
| 163 | m_references.add( String.format( "%s_<clinit>", | ||
| 164 | scrubClassName( reference.context.getClassName() ) | ||
| 165 | ) ); | ||
| 166 | } | ||
| 74 | } | 167 | } |
| 75 | 168 | ||
| 76 | public ClassEntry getClassEntry( ) | 169 | public ClassEntry getClassEntry( ) |
| @@ -109,9 +202,38 @@ public class ClassIdentity | |||
| 109 | buf.append( m_staticInitializer ); | 202 | buf.append( m_staticInitializer ); |
| 110 | buf.append( "\n" ); | 203 | buf.append( "\n" ); |
| 111 | } | 204 | } |
| 205 | if( m_extends.length() > 0 ) | ||
| 206 | { | ||
| 207 | buf.append( "\textends " ); | ||
| 208 | buf.append( m_extends ); | ||
| 209 | buf.append( "\n" ); | ||
| 210 | } | ||
| 211 | for( String interfaceName : m_implements ) | ||
| 212 | { | ||
| 213 | buf.append( "\timplements " ); | ||
| 214 | buf.append( interfaceName ); | ||
| 215 | buf.append( "\n" ); | ||
| 216 | } | ||
| 217 | for( String implementation : m_implementations ) | ||
| 218 | { | ||
| 219 | buf.append( "\timplemented by " ); | ||
| 220 | buf.append( implementation ); | ||
| 221 | buf.append( "\n" ); | ||
| 222 | } | ||
| 223 | for( String reference : m_references ) | ||
| 224 | { | ||
| 225 | buf.append( "\treference " ); | ||
| 226 | buf.append( reference ); | ||
| 227 | buf.append( "\n" ); | ||
| 228 | } | ||
| 112 | return buf.toString(); | 229 | return buf.toString(); |
| 113 | } | 230 | } |
| 114 | 231 | ||
| 232 | private String scrubClassName( String className ) | ||
| 233 | { | ||
| 234 | return scrubSignature( "L" + Descriptor.toJvmName( className ) + ";" ); | ||
| 235 | } | ||
| 236 | |||
| 115 | private String scrubSignature( String signature ) | 237 | private String scrubSignature( String signature ) |
| 116 | { | 238 | { |
| 117 | return SignatureUpdater.update( signature, new ClassNameUpdater( ) | 239 | return SignatureUpdater.update( signature, new ClassNameUpdater( ) |
| @@ -121,12 +243,30 @@ public class ClassIdentity | |||
| 121 | @Override | 243 | @Override |
| 122 | public String update( String className ) | 244 | public String update( String className ) |
| 123 | { | 245 | { |
| 124 | // does the class have a package? | 246 | // classes not in the none package can be passed through |
| 125 | if( className.indexOf( '/' ) >= 0 ) | 247 | ClassEntry classEntry = new ClassEntry( className ); |
| 248 | if( !classEntry.getPackageName().equals( Constants.NonePackage ) ) | ||
| 126 | { | 249 | { |
| 127 | return className; | 250 | return className; |
| 128 | } | 251 | } |
| 129 | 252 | ||
| 253 | // is this class ourself? | ||
| 254 | if( className.equals( m_classEntry.getName() ) ) | ||
| 255 | { | ||
| 256 | return "CSelf"; | ||
| 257 | } | ||
| 258 | |||
| 259 | // try the namer | ||
| 260 | if( m_namer != null ) | ||
| 261 | { | ||
| 262 | String newName = m_namer.getName( className ); | ||
| 263 | if( newName != null ) | ||
| 264 | { | ||
| 265 | return newName; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | // otherwise, use local naming | ||
| 130 | if( !m_classNames.containsKey( className ) ) | 270 | if( !m_classNames.containsKey( className ) ) |
| 131 | { | 271 | { |
| 132 | m_classNames.put( className, getNewClassName() ); | 272 | m_classNames.put( className, getNewClassName() ); |
| @@ -141,6 +281,11 @@ public class ClassIdentity | |||
| 141 | } ); | 281 | } ); |
| 142 | } | 282 | } |
| 143 | 283 | ||
| 284 | private boolean isClassMatchedUniquely( String className ) | ||
| 285 | { | ||
| 286 | return m_namer != null && m_namer.getName( Descriptor.toJvmName( className ) ) != null; | ||
| 287 | } | ||
| 288 | |||
| 144 | private String getBehaviorSignature( CtBehavior behavior ) | 289 | private String getBehaviorSignature( CtBehavior behavior ) |
| 145 | { | 290 | { |
| 146 | try | 291 | try |
| @@ -153,8 +298,7 @@ public class ClassIdentity | |||
| 153 | 298 | ||
| 154 | // compute the hash from the opcodes | 299 | // compute the hash from the opcodes |
| 155 | ConstPool constants = behavior.getMethodInfo().getConstPool(); | 300 | ConstPool constants = behavior.getMethodInfo().getConstPool(); |
| 156 | ConstPoolEditor editor = new ConstPoolEditor( constants ); | 301 | final MessageDigest digest = MessageDigest.getInstance( "MD5" ); |
| 157 | MessageDigest digest = MessageDigest.getInstance( "MD5" ); | ||
| 158 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); | 302 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); |
| 159 | while( iter.hasNext() ) | 303 | while( iter.hasNext() ) |
| 160 | { | 304 | { |
| @@ -164,46 +308,91 @@ public class ClassIdentity | |||
| 164 | int opcode = iter.byteAt( pos ); | 308 | int opcode = iter.byteAt( pos ); |
| 165 | digest.update( (byte)opcode ); | 309 | digest.update( (byte)opcode ); |
| 166 | 310 | ||
| 167 | // is there a constant value here? | ||
| 168 | int constIndex = -1; | ||
| 169 | switch( opcode ) | 311 | switch( opcode ) |
| 170 | { | 312 | { |
| 171 | case Opcode.LDC: | 313 | case Opcode.LDC: |
| 172 | constIndex = iter.byteAt( pos + 1 ); | 314 | { |
| 315 | int constIndex = iter.byteAt( pos + 1 ); | ||
| 316 | updateHashWithConstant( digest, constants, constIndex ); | ||
| 317 | } | ||
| 173 | break; | 318 | break; |
| 174 | 319 | ||
| 175 | case Opcode.LDC_W: | 320 | case Opcode.LDC_W: |
| 176 | constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | ||
| 177 | break; | ||
| 178 | |||
| 179 | case Opcode.LDC2_W: | 321 | case Opcode.LDC2_W: |
| 180 | constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | 322 | { |
| 323 | int constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | ||
| 324 | updateHashWithConstant( digest, constants, constIndex ); | ||
| 325 | } | ||
| 181 | break; | 326 | break; |
| 182 | } | 327 | } |
| 328 | } | ||
| 329 | |||
| 330 | // update hash with method and field accesses | ||
| 331 | behavior.instrument( new ExprEditor( ) | ||
| 332 | { | ||
| 333 | @Override | ||
| 334 | public void edit( MethodCall call ) | ||
| 335 | { | ||
| 336 | updateHashWithString( digest, scrubClassName( call.getClassName() ) ); | ||
| 337 | updateHashWithString( digest, scrubSignature( call.getSignature() ) ); | ||
| 338 | if( isClassMatchedUniquely( call.getClassName() ) ) | ||
| 339 | { | ||
| 340 | updateHashWithString( digest, call.getMethodName() ); | ||
| 341 | } | ||
| 342 | } | ||
| 183 | 343 | ||
| 184 | if( constIndex >= 0 ) | 344 | @Override |
| 345 | public void edit( FieldAccess access ) | ||
| 185 | { | 346 | { |
| 186 | // update the hash with the constant value | 347 | updateHashWithString( digest, scrubClassName( access.getClassName() ) ); |
| 187 | ConstInfoAccessor item = editor.getItem( constIndex ); | 348 | updateHashWithString( digest, scrubSignature( access.getSignature() ) ); |
| 188 | if( item.getType() == InfoType.StringInfo ) | 349 | if( isClassMatchedUniquely( access.getClassName() ) ) |
| 189 | { | 350 | { |
| 190 | String val = constants.getStringInfo( constIndex ); | 351 | updateHashWithString( digest, access.getFieldName() ); |
| 191 | try | ||
| 192 | { | ||
| 193 | digest.update( val.getBytes( "UTF8" ) ); | ||
| 194 | } | ||
| 195 | catch( UnsupportedEncodingException ex ) | ||
| 196 | { | ||
| 197 | throw new Error( ex ); | ||
| 198 | } | ||
| 199 | } | 352 | } |
| 200 | } | 353 | } |
| 201 | } | 354 | |
| 355 | @Override | ||
| 356 | public void edit( ConstructorCall call ) | ||
| 357 | { | ||
| 358 | updateHashWithString( digest, scrubClassName( call.getClassName() ) ); | ||
| 359 | updateHashWithString( digest, scrubSignature( call.getSignature() ) ); | ||
| 360 | } | ||
| 361 | |||
| 362 | @Override | ||
| 363 | public void edit( NewExpr expr ) | ||
| 364 | { | ||
| 365 | updateHashWithString( digest, scrubClassName( expr.getClassName() ) ); | ||
| 366 | } | ||
| 367 | } ); | ||
| 202 | 368 | ||
| 203 | // convert the hash to a hex string | 369 | // convert the hash to a hex string |
| 204 | return toHex( digest.digest() ); | 370 | return toHex( digest.digest() ); |
| 205 | } | 371 | } |
| 206 | catch( BadBytecode | NoSuchAlgorithmException ex ) | 372 | catch( BadBytecode | NoSuchAlgorithmException | CannotCompileException ex ) |
| 373 | { | ||
| 374 | throw new Error( ex ); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | private void updateHashWithConstant( MessageDigest digest, ConstPool constants, int index ) | ||
| 379 | { | ||
| 380 | ConstPoolEditor editor = new ConstPoolEditor( constants ); | ||
| 381 | ConstInfoAccessor item = editor.getItem( index ); | ||
| 382 | if( item.getType() == InfoType.StringInfo ) | ||
| 383 | { | ||
| 384 | updateHashWithString( digest, constants.getStringInfo( index ) ); | ||
| 385 | } | ||
| 386 | // TODO: other constants | ||
| 387 | } | ||
| 388 | |||
| 389 | private void updateHashWithString( MessageDigest digest, String val ) | ||
| 390 | { | ||
| 391 | try | ||
| 392 | { | ||
| 393 | digest.update( val.getBytes( "UTF8" ) ); | ||
| 394 | } | ||
| 395 | catch( UnsupportedEncodingException ex ) | ||
| 207 | { | 396 | { |
| 208 | throw new Error( ex ); | 397 | throw new Error( ex ); |
| 209 | } | 398 | } |
| @@ -239,7 +428,11 @@ public class ClassIdentity | |||
| 239 | return m_fields.equals( other.m_fields ) | 428 | return m_fields.equals( other.m_fields ) |
| 240 | && m_methods.equals( other.m_methods ) | 429 | && m_methods.equals( other.m_methods ) |
| 241 | && m_constructors.equals( other.m_constructors ) | 430 | && m_constructors.equals( other.m_constructors ) |
| 242 | && m_staticInitializer.equals( other.m_staticInitializer ); | 431 | && m_staticInitializer.equals( other.m_staticInitializer ) |
| 432 | && m_extends.equals( other.m_extends ) | ||
| 433 | && m_implements.equals( other.m_implements ) | ||
| 434 | && m_implementations.equals( other.m_implementations ) | ||
| 435 | && m_references.equals( other.m_references ); | ||
| 243 | } | 436 | } |
| 244 | 437 | ||
| 245 | @Override | 438 | @Override |
| @@ -250,6 +443,10 @@ public class ClassIdentity | |||
| 250 | objs.addAll( m_methods ); | 443 | objs.addAll( m_methods ); |
| 251 | objs.addAll( m_constructors ); | 444 | objs.addAll( m_constructors ); |
| 252 | objs.add( m_staticInitializer ); | 445 | objs.add( m_staticInitializer ); |
| 446 | objs.add( m_extends ); | ||
| 447 | objs.addAll( m_implements ); | ||
| 448 | objs.addAll( m_implementations ); | ||
| 449 | objs.addAll( m_references ); | ||
| 253 | return Util.combineHashesOrdered( objs ); | 450 | return Util.combineHashesOrdered( objs ); |
| 254 | } | 451 | } |
| 255 | } | 452 | } |