diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/ClassIdentity.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/convert/ClassIdentity.java | 821 |
1 files changed, 410 insertions, 411 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java index f72bf70..a395b75 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java | |||
| @@ -8,18 +8,10 @@ | |||
| 8 | * Contributors: | 8 | * Contributors: |
| 9 | * Jeff Martin - initial API and implementation | 9 | * Jeff Martin - initial API and implementation |
| 10 | ******************************************************************************/ | 10 | ******************************************************************************/ |
| 11 | |||
| 11 | package cuchaz.enigma.convert; | 12 | package cuchaz.enigma.convert; |
| 12 | 13 | ||
| 13 | import com.google.common.collect.*; | 14 | import com.google.common.collect.*; |
| 14 | |||
| 15 | import java.io.UnsupportedEncodingException; | ||
| 16 | import java.security.MessageDigest; | ||
| 17 | import java.security.NoSuchAlgorithmException; | ||
| 18 | import java.util.Enumeration; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.Map; | ||
| 21 | import java.util.Set; | ||
| 22 | |||
| 23 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | 15 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; |
| 24 | import cuchaz.enigma.analysis.EntryReference; | 16 | import cuchaz.enigma.analysis.EntryReference; |
| 25 | import cuchaz.enigma.analysis.JarIndex; | 17 | import cuchaz.enigma.analysis.JarIndex; |
| @@ -33,408 +25,415 @@ import javassist.*; | |||
| 33 | import javassist.bytecode.*; | 25 | import javassist.bytecode.*; |
| 34 | import javassist.expr.*; | 26 | import javassist.expr.*; |
| 35 | 27 | ||
| 28 | import java.io.UnsupportedEncodingException; | ||
| 29 | import java.security.MessageDigest; | ||
| 30 | import java.security.NoSuchAlgorithmException; | ||
| 31 | import java.util.Enumeration; | ||
| 32 | import java.util.List; | ||
| 33 | import java.util.Map; | ||
| 34 | import java.util.Set; | ||
| 35 | |||
| 36 | public class ClassIdentity { | 36 | public class ClassIdentity { |
| 37 | 37 | ||
| 38 | private ClassEntry classEntry; | 38 | private ClassEntry classEntry; |
| 39 | private SidedClassNamer namer; | 39 | private SidedClassNamer namer; |
| 40 | private Multiset<String> fields; | 40 | private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { |
| 41 | private Multiset<String> methods; | 41 | |
| 42 | private Multiset<String> constructors; | 42 | private Map<String, String> classNames = Maps.newHashMap(); |
| 43 | private String staticInitializer; | 43 | |
| 44 | private String extendz; | 44 | @Override |
| 45 | private Multiset<String> implementz; | 45 | public String replace(String className) { |
| 46 | private Set<String> stringLiterals; | 46 | |
| 47 | private Multiset<String> implementations; | 47 | // classes not in the none package can be passed through |
| 48 | private Multiset<String> references; | 48 | ClassEntry classEntry = new ClassEntry(className); |
| 49 | private String outer; | 49 | if (classEntry.getPackageName() != null) { |
| 50 | 50 | return className; | |
| 51 | private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { | 51 | } |
| 52 | 52 | ||
| 53 | private Map<String, String> classNames = Maps.newHashMap(); | 53 | // is this class ourself? |
| 54 | 54 | if (className.equals(classEntry.getName())) { | |
| 55 | @Override | 55 | return "CSelf"; |
| 56 | public String replace(String className) { | 56 | } |
| 57 | 57 | ||
| 58 | // classes not in the none package can be passed through | 58 | // try the namer |
| 59 | ClassEntry classEntry = new ClassEntry(className); | 59 | if (namer != null) { |
| 60 | if (classEntry.getPackageName() != null) { | 60 | String newName = namer.getName(className); |
| 61 | return className; | 61 | if (newName != null) { |
| 62 | } | 62 | return newName; |
| 63 | 63 | } | |
| 64 | // is this class ourself? | 64 | } |
| 65 | if (className.equals(classEntry.getName())) { | 65 | |
| 66 | return "CSelf"; | 66 | // otherwise, use local naming |
| 67 | } | 67 | if (!classNames.containsKey(className)) { |
| 68 | 68 | classNames.put(className, getNewClassName()); | |
| 69 | // try the namer | 69 | } |
| 70 | if (namer != null) { | 70 | return classNames.get(className); |
| 71 | String newName = namer.getName(className); | 71 | } |
| 72 | if (newName != null) { | 72 | |
| 73 | return newName; | 73 | private String getNewClassName() { |
| 74 | } | 74 | return String.format("C%03d", classNames.size()); |
| 75 | } | 75 | } |
| 76 | 76 | }; | |
| 77 | // otherwise, use local naming | 77 | private Multiset<String> fields; |
| 78 | if (!classNames.containsKey(className)) { | 78 | private Multiset<String> methods; |
| 79 | classNames.put(className, getNewClassName()); | 79 | private Multiset<String> constructors; |
| 80 | } | 80 | private String staticInitializer; |
| 81 | return classNames.get(className); | 81 | private String extendz; |
| 82 | } | 82 | private Multiset<String> implementz; |
| 83 | 83 | private Set<String> stringLiterals; | |
| 84 | private String getNewClassName() { | 84 | private Multiset<String> implementations; |
| 85 | return String.format("C%03d", classNames.size()); | 85 | private Multiset<String> references; |
| 86 | } | 86 | private String outer; |
| 87 | }; | 87 | |
| 88 | 88 | public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { | |
| 89 | public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { | 89 | this.namer = namer; |
| 90 | this.namer = namer; | 90 | |
| 91 | 91 | // stuff from the bytecode | |
| 92 | // stuff from the bytecode | 92 | |
| 93 | 93 | this.classEntry = EntryFactory.getClassEntry(c); | |
| 94 | this.classEntry = EntryFactory.getClassEntry(c); | 94 | this.fields = HashMultiset.create(); |
| 95 | this.fields = HashMultiset.create(); | 95 | for (CtField field : c.getDeclaredFields()) { |
| 96 | for (CtField field : c.getDeclaredFields()) { | 96 | this.fields.add(scrubType(field.getSignature())); |
| 97 | this.fields.add(scrubType(field.getSignature())); | 97 | } |
| 98 | } | 98 | this.methods = HashMultiset.create(); |
| 99 | this.methods = HashMultiset.create(); | 99 | for (CtMethod method : c.getDeclaredMethods()) { |
| 100 | for (CtMethod method : c.getDeclaredMethods()) { | 100 | this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); |
| 101 | this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); | 101 | } |
| 102 | } | 102 | this.constructors = HashMultiset.create(); |
| 103 | this.constructors = HashMultiset.create(); | 103 | for (CtConstructor constructor : c.getDeclaredConstructors()) { |
| 104 | for (CtConstructor constructor : c.getDeclaredConstructors()) { | 104 | this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); |
| 105 | this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); | 105 | } |
| 106 | } | 106 | this.staticInitializer = ""; |
| 107 | this.staticInitializer = ""; | 107 | if (c.getClassInitializer() != null) { |
| 108 | if (c.getClassInitializer() != null) { | 108 | this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); |
| 109 | this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); | 109 | } |
| 110 | } | 110 | this.extendz = ""; |
| 111 | this.extendz = ""; | 111 | if (c.getClassFile().getSuperclass() != null) { |
| 112 | if (c.getClassFile().getSuperclass() != null) { | 112 | this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); |
| 113 | this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); | 113 | } |
| 114 | } | 114 | this.implementz = HashMultiset.create(); |
| 115 | this.implementz = HashMultiset.create(); | 115 | for (String interfaceName : c.getClassFile().getInterfaces()) { |
| 116 | for (String interfaceName : c.getClassFile().getInterfaces()) { | 116 | this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); |
| 117 | this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); | 117 | } |
| 118 | } | 118 | |
| 119 | 119 | this.stringLiterals = Sets.newHashSet(); | |
| 120 | this.stringLiterals = Sets.newHashSet(); | 120 | ConstPool constants = c.getClassFile().getConstPool(); |
| 121 | ConstPool constants = c.getClassFile().getConstPool(); | 121 | for (int i = 1; i < constants.getSize(); i++) { |
| 122 | for (int i = 1; i < constants.getSize(); i++) { | 122 | if (constants.getTag(i) == ConstPool.CONST_String) { |
| 123 | if (constants.getTag(i) == ConstPool.CONST_String) { | 123 | this.stringLiterals.add(constants.getStringInfo(i)); |
| 124 | this.stringLiterals.add(constants.getStringInfo(i)); | 124 | } |
| 125 | } | 125 | } |
| 126 | } | 126 | |
| 127 | 127 | // stuff from the jar index | |
| 128 | // stuff from the jar index | 128 | |
| 129 | 129 | this.implementations = HashMultiset.create(); | |
| 130 | this.implementations = HashMultiset.create(); | 130 | ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); |
| 131 | ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); | 131 | if (implementationsNode != null) { |
| 132 | if (implementationsNode != null) { | 132 | @SuppressWarnings("unchecked") |
| 133 | @SuppressWarnings("unchecked") | 133 | Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children(); |
| 134 | Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children(); | 134 | while (implementations.hasMoreElements()) { |
| 135 | while (implementations.hasMoreElements()) { | 135 | ClassImplementationsTreeNode node = implementations.nextElement(); |
| 136 | ClassImplementationsTreeNode node = implementations.nextElement(); | 136 | this.implementations.add(scrubClassName(node.getClassEntry().getName())); |
| 137 | this.implementations.add(scrubClassName(node.getClassEntry().getName())); | 137 | } |
| 138 | } | 138 | } |
| 139 | } | 139 | |
| 140 | 140 | this.references = HashMultiset.create(); | |
| 141 | this.references = HashMultiset.create(); | 141 | if (useReferences) { |
| 142 | if (useReferences) { | 142 | for (CtField field : c.getDeclaredFields()) { |
| 143 | for (CtField field : c.getDeclaredFields()) { | 143 | FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); |
| 144 | FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); | 144 | index.getFieldReferences(fieldEntry).forEach(this::addReference); |
| 145 | index.getFieldReferences(fieldEntry).forEach(this::addReference); | 145 | } |
| 146 | } | 146 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { |
| 147 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | 147 | BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); |
| 148 | BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); | 148 | index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); |
| 149 | index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); | 149 | } |
| 150 | } | 150 | } |
| 151 | } | 151 | |
| 152 | 152 | this.outer = null; | |
| 153 | this.outer = null; | 153 | if (this.classEntry.isInnerClass()) { |
| 154 | if (this.classEntry.isInnerClass()) { | 154 | this.outer = this.classEntry.getOuterClassName(); |
| 155 | this.outer = this.classEntry.getOuterClassName(); | 155 | } |
| 156 | } | 156 | } |
| 157 | } | 157 | |
| 158 | 158 | private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) { | |
| 159 | private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) { | 159 | if (reference.context.getSignature() != null) { |
| 160 | if (reference.context.getSignature() != null) { | 160 | this.references.add(String.format("%s_%s", |
| 161 | this.references.add(String.format("%s_%s", | 161 | scrubClassName(reference.context.getClassName()), |
| 162 | scrubClassName(reference.context.getClassName()), | 162 | scrubSignature(reference.context.getSignature()) |
| 163 | scrubSignature(reference.context.getSignature()) | 163 | )); |
| 164 | )); | 164 | } else { |
| 165 | } else { | 165 | this.references.add(String.format("%s_<clinit>", |
| 166 | this.references.add(String.format("%s_<clinit>", | 166 | scrubClassName(reference.context.getClassName()) |
| 167 | scrubClassName(reference.context.getClassName()) | 167 | )); |
| 168 | )); | 168 | } |
| 169 | } | 169 | } |
| 170 | } | 170 | |
| 171 | 171 | public ClassEntry getClassEntry() { | |
| 172 | public ClassEntry getClassEntry() { | 172 | return this.classEntry; |
| 173 | return this.classEntry; | 173 | } |
| 174 | } | 174 | |
| 175 | 175 | @Override | |
| 176 | @Override | 176 | public String toString() { |
| 177 | public String toString() { | 177 | StringBuilder buf = new StringBuilder(); |
| 178 | StringBuilder buf = new StringBuilder(); | 178 | buf.append("class: "); |
| 179 | buf.append("class: "); | 179 | buf.append(this.classEntry.getName()); |
| 180 | buf.append(this.classEntry.getName()); | 180 | buf.append(" "); |
| 181 | buf.append(" "); | 181 | buf.append(hashCode()); |
| 182 | buf.append(hashCode()); | 182 | buf.append("\n"); |
| 183 | buf.append("\n"); | 183 | for (String field : this.fields) { |
| 184 | for (String field : this.fields) { | 184 | buf.append("\tfield "); |
| 185 | buf.append("\tfield "); | 185 | buf.append(field); |
| 186 | buf.append(field); | 186 | buf.append("\n"); |
| 187 | buf.append("\n"); | 187 | } |
| 188 | } | 188 | for (String method : this.methods) { |
| 189 | for (String method : this.methods) { | 189 | buf.append("\tmethod "); |
| 190 | buf.append("\tmethod "); | 190 | buf.append(method); |
| 191 | buf.append(method); | 191 | buf.append("\n"); |
| 192 | buf.append("\n"); | 192 | } |
| 193 | } | 193 | for (String constructor : this.constructors) { |
| 194 | for (String constructor : this.constructors) { | 194 | buf.append("\tconstructor "); |
| 195 | buf.append("\tconstructor "); | 195 | buf.append(constructor); |
| 196 | buf.append(constructor); | 196 | buf.append("\n"); |
| 197 | buf.append("\n"); | 197 | } |
| 198 | } | 198 | if (!this.staticInitializer.isEmpty()) { |
| 199 | if (this.staticInitializer.length() > 0) { | 199 | buf.append("\tinitializer "); |
| 200 | buf.append("\tinitializer "); | 200 | buf.append(this.staticInitializer); |
| 201 | buf.append(this.staticInitializer); | 201 | buf.append("\n"); |
| 202 | buf.append("\n"); | 202 | } |
| 203 | } | 203 | if (!this.extendz.isEmpty()) { |
| 204 | if (this.extendz.length() > 0) { | 204 | buf.append("\textends "); |
| 205 | buf.append("\textends "); | 205 | buf.append(this.extendz); |
| 206 | buf.append(this.extendz); | 206 | buf.append("\n"); |
| 207 | buf.append("\n"); | 207 | } |
| 208 | } | 208 | for (String interfaceName : this.implementz) { |
| 209 | for (String interfaceName : this.implementz) { | 209 | buf.append("\timplements "); |
| 210 | buf.append("\timplements "); | 210 | buf.append(interfaceName); |
| 211 | buf.append(interfaceName); | 211 | buf.append("\n"); |
| 212 | buf.append("\n"); | 212 | } |
| 213 | } | 213 | for (String implementation : this.implementations) { |
| 214 | for (String implementation : this.implementations) { | 214 | buf.append("\timplemented by "); |
| 215 | buf.append("\timplemented by "); | 215 | buf.append(implementation); |
| 216 | buf.append(implementation); | 216 | buf.append("\n"); |
| 217 | buf.append("\n"); | 217 | } |
| 218 | } | 218 | for (String reference : this.references) { |
| 219 | for (String reference : this.references) { | 219 | buf.append("\treference "); |
| 220 | buf.append("\treference "); | 220 | buf.append(reference); |
| 221 | buf.append(reference); | 221 | buf.append("\n"); |
| 222 | buf.append("\n"); | 222 | } |
| 223 | } | 223 | buf.append("\touter "); |
| 224 | buf.append("\touter "); | 224 | buf.append(this.outer); |
| 225 | buf.append(this.outer); | 225 | buf.append("\n"); |
| 226 | buf.append("\n"); | 226 | return buf.toString(); |
| 227 | return buf.toString(); | 227 | } |
| 228 | } | 228 | |
| 229 | 229 | private String scrubClassName(String className) { | |
| 230 | private String scrubClassName(String className) { | 230 | return classNameReplacer.replace(className); |
| 231 | return classNameReplacer.replace(className); | 231 | } |
| 232 | } | 232 | |
| 233 | 233 | private String scrubType(String typeName) { | |
| 234 | private String scrubType(String typeName) { | 234 | return scrubType(new Type(typeName)).toString(); |
| 235 | return scrubType(new Type(typeName)).toString(); | 235 | } |
| 236 | } | 236 | |
| 237 | 237 | private Type scrubType(Type type) { | |
| 238 | private Type scrubType(Type type) { | 238 | if (type.hasClass()) { |
| 239 | if (type.hasClass()) { | 239 | return new Type(type, classNameReplacer); |
| 240 | return new Type(type, classNameReplacer); | 240 | } else { |
| 241 | } else { | 241 | return type; |
| 242 | return type; | 242 | } |
| 243 | } | 243 | } |
| 244 | } | 244 | |
| 245 | 245 | private String scrubSignature(String signature) { | |
| 246 | private String scrubSignature(String signature) { | 246 | return scrubSignature(new Signature(signature)).toString(); |
| 247 | return scrubSignature(new Signature(signature)).toString(); | 247 | } |
| 248 | } | 248 | |
| 249 | 249 | private Signature scrubSignature(Signature signature) { | |
| 250 | private Signature scrubSignature(Signature signature) { | 250 | return new Signature(signature, classNameReplacer); |
| 251 | return new Signature(signature, classNameReplacer); | 251 | } |
| 252 | } | 252 | |
| 253 | 253 | private boolean isClassMatchedUniquely(String className) { | |
| 254 | private boolean isClassMatchedUniquely(String className) { | 254 | return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; |
| 255 | return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; | 255 | } |
| 256 | } | 256 | |
| 257 | 257 | private String getBehaviorSignature(CtBehavior behavior) { | |
| 258 | private String getBehaviorSignature(CtBehavior behavior) { | 258 | try { |
| 259 | try { | 259 | // does this method have an implementation? |
| 260 | // does this method have an implementation? | 260 | if (behavior.getMethodInfo().getCodeAttribute() == null) { |
| 261 | if (behavior.getMethodInfo().getCodeAttribute() == null) { | 261 | return "(none)"; |
| 262 | return "(none)"; | 262 | } |
| 263 | } | 263 | |
| 264 | 264 | // compute the hash from the opcodes | |
| 265 | // compute the hash from the opcodes | 265 | ConstPool constants = behavior.getMethodInfo().getConstPool(); |
| 266 | ConstPool constants = behavior.getMethodInfo().getConstPool(); | 266 | final MessageDigest digest = MessageDigest.getInstance("MD5"); |
| 267 | final MessageDigest digest = MessageDigest.getInstance("MD5"); | 267 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); |
| 268 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); | 268 | while (iter.hasNext()) { |
| 269 | while (iter.hasNext()) { | 269 | int pos = iter.next(); |
| 270 | int pos = iter.next(); | 270 | |
| 271 | 271 | // update the hash with the opcode | |
| 272 | // update the hash with the opcode | 272 | int opcode = iter.byteAt(pos); |
| 273 | int opcode = iter.byteAt(pos); | 273 | digest.update((byte) opcode); |
| 274 | digest.update((byte) opcode); | 274 | int constIndex; |
| 275 | int constIndex; | 275 | switch (opcode) { |
| 276 | switch (opcode) { | 276 | case Opcode.LDC: |
| 277 | case Opcode.LDC: | 277 | constIndex = iter.byteAt(pos + 1); |
| 278 | constIndex = iter.byteAt(pos + 1); | 278 | updateHashWithConstant(digest, constants, constIndex); |
| 279 | updateHashWithConstant(digest, constants, constIndex); | 279 | break; |
| 280 | break; | 280 | |
| 281 | 281 | case Opcode.LDC_W: | |
| 282 | case Opcode.LDC_W: | 282 | case Opcode.LDC2_W: |
| 283 | case Opcode.LDC2_W: | 283 | constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); |
| 284 | constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); | 284 | updateHashWithConstant(digest, constants, constIndex); |
| 285 | updateHashWithConstant(digest, constants, constIndex); | 285 | break; |
| 286 | break; | 286 | default: |
| 287 | default: | 287 | break; |
| 288 | break; | 288 | } |
| 289 | } | 289 | } |
| 290 | } | 290 | |
| 291 | 291 | // update hash with method and field accesses | |
| 292 | // update hash with method and field accesses | 292 | behavior.instrument(new ExprEditor() { |
| 293 | behavior.instrument(new ExprEditor() { | 293 | @Override |
| 294 | @Override | 294 | public void edit(MethodCall call) { |
| 295 | public void edit(MethodCall call) { | 295 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); |
| 296 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); | 296 | updateHashWithString(digest, scrubSignature(call.getSignature())); |
| 297 | updateHashWithString(digest, scrubSignature(call.getSignature())); | 297 | if (isClassMatchedUniquely(call.getClassName())) { |
| 298 | if (isClassMatchedUniquely(call.getClassName())) { | 298 | updateHashWithString(digest, call.getMethodName()); |
| 299 | updateHashWithString(digest, call.getMethodName()); | 299 | } |
| 300 | } | 300 | } |
| 301 | } | 301 | |
| 302 | 302 | @Override | |
| 303 | @Override | 303 | public void edit(FieldAccess access) { |
| 304 | public void edit(FieldAccess access) { | 304 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); |
| 305 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); | 305 | updateHashWithString(digest, scrubType(access.getSignature())); |
| 306 | updateHashWithString(digest, scrubType(access.getSignature())); | 306 | if (isClassMatchedUniquely(access.getClassName())) { |
| 307 | if (isClassMatchedUniquely(access.getClassName())) { | 307 | updateHashWithString(digest, access.getFieldName()); |
| 308 | updateHashWithString(digest, access.getFieldName()); | 308 | } |
| 309 | } | 309 | } |
| 310 | } | 310 | |
| 311 | 311 | @Override | |
| 312 | @Override | 312 | public void edit(ConstructorCall call) { |
| 313 | public void edit(ConstructorCall call) { | 313 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); |
| 314 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); | 314 | updateHashWithString(digest, scrubSignature(call.getSignature())); |
| 315 | updateHashWithString(digest, scrubSignature(call.getSignature())); | 315 | } |
| 316 | } | 316 | |
| 317 | 317 | @Override | |
| 318 | @Override | 318 | public void edit(NewExpr expr) { |
| 319 | public void edit(NewExpr expr) { | 319 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); |
| 320 | updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); | 320 | } |
| 321 | } | 321 | }); |
| 322 | }); | 322 | |
| 323 | 323 | // convert the hash to a hex string | |
| 324 | // convert the hash to a hex string | 324 | return toHex(digest.digest()); |
| 325 | return toHex(digest.digest()); | 325 | } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { |
| 326 | } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { | 326 | throw new Error(ex); |
| 327 | throw new Error(ex); | 327 | } |
| 328 | } | 328 | } |
| 329 | } | 329 | |
| 330 | 330 | private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { | |
| 331 | private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { | 331 | ConstPoolEditor editor = new ConstPoolEditor(constants); |
| 332 | ConstPoolEditor editor = new ConstPoolEditor(constants); | 332 | ConstInfoAccessor item = editor.getItem(index); |
| 333 | ConstInfoAccessor item = editor.getItem(index); | 333 | if (item.getType() == InfoType.StringInfo) { |
| 334 | if (item.getType() == InfoType.StringInfo) { | 334 | updateHashWithString(digest, constants.getStringInfo(index)); |
| 335 | updateHashWithString(digest, constants.getStringInfo(index)); | 335 | } |
| 336 | } | 336 | // TODO: other constants |
| 337 | // TODO: other constants | 337 | } |
| 338 | } | 338 | |
| 339 | 339 | private void updateHashWithString(MessageDigest digest, String val) { | |
| 340 | private void updateHashWithString(MessageDigest digest, String val) { | 340 | try { |
| 341 | try { | 341 | digest.update(val.getBytes("UTF8")); |
| 342 | digest.update(val.getBytes("UTF8")); | 342 | } catch (UnsupportedEncodingException ex) { |
| 343 | } catch (UnsupportedEncodingException ex) { | 343 | throw new Error(ex); |
| 344 | throw new Error(ex); | 344 | } |
| 345 | } | 345 | } |
| 346 | } | 346 | |
| 347 | 347 | private String toHex(byte[] bytes) { | |
| 348 | private String toHex(byte[] bytes) { | 348 | // function taken from: |
| 349 | // function taken from: | 349 | // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java |
| 350 | // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java | 350 | final char[] hexArray = "0123456789ABCDEF".toCharArray(); |
| 351 | final char[] hexArray = "0123456789ABCDEF".toCharArray(); | 351 | char[] hexChars = new char[bytes.length * 2]; |
| 352 | char[] hexChars = new char[bytes.length * 2]; | 352 | for (int j = 0; j < bytes.length; j++) { |
| 353 | for (int j = 0; j < bytes.length; j++) { | 353 | int v = bytes[j] & 0xFF; |
| 354 | int v = bytes[j] & 0xFF; | 354 | hexChars[j * 2] = hexArray[v >>> 4]; |
| 355 | hexChars[j * 2] = hexArray[v >>> 4]; | 355 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; |
| 356 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; | 356 | } |
| 357 | } | 357 | return new String(hexChars); |
| 358 | return new String(hexChars); | 358 | } |
| 359 | } | 359 | |
| 360 | 360 | @Override | |
| 361 | @Override | 361 | public boolean equals(Object other) { |
| 362 | public boolean equals(Object other) { | 362 | return other instanceof ClassIdentity && equals((ClassIdentity) other); |
| 363 | return other instanceof ClassIdentity && equals((ClassIdentity) other); | 363 | } |
| 364 | } | 364 | |
| 365 | 365 | public boolean equals(ClassIdentity other) { | |
| 366 | public boolean equals(ClassIdentity other) { | 366 | return this.fields.equals(other.fields) |
| 367 | return this.fields.equals(other.fields) | 367 | && this.methods.equals(other.methods) |
| 368 | && this.methods.equals(other.methods) | 368 | && this.constructors.equals(other.constructors) |
| 369 | && this.constructors.equals(other.constructors) | 369 | && this.staticInitializer.equals(other.staticInitializer) |
| 370 | && this.staticInitializer.equals(other.staticInitializer) | 370 | && this.extendz.equals(other.extendz) |
| 371 | && this.extendz.equals(other.extendz) | 371 | && this.implementz.equals(other.implementz) |
| 372 | && this.implementz.equals(other.implementz) | 372 | && this.implementations.equals(other.implementations) |
| 373 | && this.implementations.equals(other.implementations) | 373 | && this.references.equals(other.references); |
| 374 | && this.references.equals(other.references); | 374 | } |
| 375 | } | 375 | |
| 376 | 376 | @Override | |
| 377 | @Override | 377 | public int hashCode() { |
| 378 | public int hashCode() { | 378 | List<Object> objs = Lists.newArrayList(); |
| 379 | List<Object> objs = Lists.newArrayList(); | 379 | objs.addAll(this.fields); |
| 380 | objs.addAll(this.fields); | 380 | objs.addAll(this.methods); |
| 381 | objs.addAll(this.methods); | 381 | objs.addAll(this.constructors); |
| 382 | objs.addAll(this.constructors); | 382 | objs.add(this.staticInitializer); |
| 383 | objs.add(this.staticInitializer); | 383 | objs.add(this.extendz); |
| 384 | objs.add(this.extendz); | 384 | objs.addAll(this.implementz); |
| 385 | objs.addAll(this.implementz); | 385 | objs.addAll(this.implementations); |
| 386 | objs.addAll(this.implementations); | 386 | objs.addAll(this.references); |
| 387 | objs.addAll(this.references); | 387 | return Utils.combineHashesOrdered(objs); |
| 388 | return Utils.combineHashesOrdered(objs); | 388 | } |
| 389 | } | 389 | |
| 390 | 390 | public int getMatchScore(ClassIdentity other) { | |
| 391 | public int getMatchScore(ClassIdentity other) { | 391 | return 2 * getNumMatches(this.extendz, other.extendz) |
| 392 | return 2 * getNumMatches(this.extendz, other.extendz) | 392 | + 2 * getNumMatches(this.outer, other.outer) |
| 393 | + 2 * getNumMatches(this.outer, other.outer) | 393 | + 2 * getNumMatches(this.implementz, other.implementz) |
| 394 | + 2 * getNumMatches(this.implementz, other.implementz) | 394 | + getNumMatches(this.stringLiterals, other.stringLiterals) |
| 395 | + getNumMatches(this.stringLiterals, other.stringLiterals) | 395 | + getNumMatches(this.fields, other.fields) |
| 396 | + getNumMatches(this.fields, other.fields) | 396 | + getNumMatches(this.methods, other.methods) |
| 397 | + getNumMatches(this.methods, other.methods) | 397 | + getNumMatches(this.constructors, other.constructors); |
| 398 | + getNumMatches(this.constructors, other.constructors); | 398 | } |
| 399 | } | 399 | |
| 400 | 400 | public int getMaxMatchScore() { | |
| 401 | public int getMaxMatchScore() { | 401 | return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); |
| 402 | return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); | 402 | } |
| 403 | } | 403 | |
| 404 | 404 | public boolean matches(CtClass c) { | |
| 405 | public boolean matches(CtClass c) { | 405 | // just compare declaration counts |
| 406 | // just compare declaration counts | 406 | return this.fields.size() == c.getDeclaredFields().length |
| 407 | return this.fields.size() == c.getDeclaredFields().length | 407 | && this.methods.size() == c.getDeclaredMethods().length |
| 408 | && this.methods.size() == c.getDeclaredMethods().length | 408 | && this.constructors.size() == c.getDeclaredConstructors().length; |
| 409 | && this.constructors.size() == c.getDeclaredConstructors().length; | 409 | } |
| 410 | } | 410 | |
| 411 | 411 | private int getNumMatches(Set<String> a, Set<String> b) { | |
| 412 | private int getNumMatches(Set<String> a, Set<String> b) { | 412 | int numMatches = 0; |
| 413 | int numMatches = 0; | 413 | for (String val : a) { |
| 414 | for (String val : a) { | 414 | if (b.contains(val)) { |
| 415 | if (b.contains(val)) { | 415 | numMatches++; |
| 416 | numMatches++; | 416 | } |
| 417 | } | 417 | } |
| 418 | } | 418 | return numMatches; |
| 419 | return numMatches; | 419 | } |
| 420 | } | 420 | |
| 421 | 421 | private int getNumMatches(Multiset<String> a, Multiset<String> b) { | |
| 422 | private int getNumMatches(Multiset<String> a, Multiset<String> b) { | 422 | int numMatches = 0; |
| 423 | int numMatches = 0; | 423 | for (String val : a) { |
| 424 | for (String val : a) { | 424 | if (b.contains(val)) { |
| 425 | if (b.contains(val)) { | 425 | numMatches++; |
| 426 | numMatches++; | 426 | } |
| 427 | } | 427 | } |
| 428 | } | 428 | return numMatches; |
| 429 | return numMatches; | 429 | } |
| 430 | } | 430 | |
| 431 | 431 | private int getNumMatches(String a, String b) { | |
| 432 | private int getNumMatches(String a, String b) { | 432 | if (a == null && b == null) { |
| 433 | if (a == null && b == null) { | 433 | return 1; |
| 434 | return 1; | 434 | } else if (a != null && b != null && a.equals(b)) { |
| 435 | } else if (a != null && b != null && a.equals(b)) { | 435 | return 1; |
| 436 | return 1; | 436 | } |
| 437 | } | 437 | return 0; |
| 438 | return 0; | 438 | } |
| 439 | } | ||
| 440 | } | 439 | } |