/*******************************************************************************
* Copyright (c) 2010 Eric Bodden.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eric Bodden - initial API and implementation
******************************************************************************/
package de.bodden.tamiflex.normalizer;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;
import de.bodden.tamiflex.normalizer.ClassRenamer.NoHashedNameException;
public class Hasher {
protected final static Map<String,String> generatedClassNameToHashedClassName = new HashMap<String, String>();
protected final static Map<String,byte[]> hashedClassNameToOriginalBytes = new HashMap<String, byte[]>();
/**
* Classes containing these strings are blacklisted, i.e. calls to these classes will not be written to the log.
* Further, these classes will not be written to disk.
* This is because the classes are "unstable". They are generated and therefore can change from one run to another.
* In particular, the numbered suffixed of the names of these classes can easily change.
*/
protected static String[] instableNames = {
"GeneratedConstructorAccessor",
"GeneratedMethodAccessor",
"GeneratedSerializationConstructorAccessor",
"ByCGLIB",
"org/apache/derby/exe/",
"$Proxy" /*,
"schemaorg_apache_xmlbeans/system/" these names seem to be stable, as they are already hashed */
};
public static void dontNormalize() {
instableNames = new String[0];
}
public synchronized static void generateHashNumber(final String theClassName, byte[] classBytes) throws NoHashedNameException {
boolean usingAssertions = false; assert usingAssertions = true;
//if we don't use assertions then simply return if the hash code was alread computed
if(!usingAssertions && generatedClassNameToHashedClassName.containsKey(theClassName)) return;
assert containsGeneratedClassName(theClassName) : "Class "+theClassName+" contains no generated name.";
ClassReader creader = new ClassReader(classBytes);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
RemappingClassAdapter visitor = new RemappingClassAdapter(writer,new Remapper(){
@Override
public String map(String typeName) {
if(theClassName.equals(typeName)) return "$$$NORMALIZED$$$";
String newName = generatedClassNameToHashedClassName.get(typeName);
if(Hasher.containsGeneratedClassName(typeName) && newName==null) {
throw new NoHashedNameException(typeName);
}
if(newName!=null) typeName = newName;
return super.map(typeName);
}
}) {
@Override
public void visitSource(String source, String debug) {
/* we ignore the source-file attribute during hashing;
* the position at which this attribute is inserted is kind of random,
* and can therefore lead to unwanted noise */
}
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv = new RemappingStringConstantAdapter(mv, new StringRemapper() {
@Override
public String remapStringConstant(String constant) {
String slashed = slashed(constant);
if(theClassName.equals(slashed)) return "$$$NORMALIZED$$$";
String to = generatedClassNameToHashedClassName.get(slashed);
if(!theClassName.equals(slashed) && Hasher.containsGeneratedClassName(slashed) && to==null) {
throw new NoHashedNameException(slashed);
}
if(to!=null) constant = dotted(to);
return super.remapStringConstant(constant);
}
});
return mv;
}
};
creader.accept(visitor, 0);
byte[] renamed = writer.toByteArray();
////////
String hash = SHAHash.SHA1(renamed);
for(String infix: instableNames) {
if(theClassName.contains(infix)) {
String hashedName = theClassName.substring(0, theClassName.indexOf(infix)+infix.length()) + "$HASHED$" + hash;
assert !generatedClassNameToHashedClassName.containsKey(theClassName)
|| generatedClassNameToHashedClassName.get(theClassName).equals(hashedName) :
"Hashed names not stable for "+theClassName+": "+generatedClassNameToHashedClassName.get(theClassName)+","+hashedName;
generatedClassNameToHashedClassName.put(theClassName, hashedName);
}
}
assert generatedClassNameToHashedClassName.containsKey(theClassName);
}
public static boolean containsGeneratedClassName(String className) {
assert !className.contains(".") : "Class name must contain slashes, not dots: "+className;
for(String name: instableNames) {
if(className.contains(name))
return true;
}
return false;
}
public static String hashedClassNameForGeneratedClassName(String className) {
assert !className.contains(".") : "Class name must contain slashes, not dots: "+className;
assert containsGeneratedClassName(className) : "No generated class name: "+className;
String hashedName = generatedClassNameToHashedClassName.get(className);
assert hashedName != null : "No hashed class name for generated class: "+className;
return hashedName;
}
public static byte[] replaceGeneratedClassNamesByHashedNames(byte[] classBytes) {
return ClassRenamer.replaceClassNamesInBytes(generatedClassNameToHashedClassName,classBytes);
}
public static String dotted(String className) {
return className.replace('/', '.');
}
public static String slashed(String className) {
return className.replace('.', '/');
}
}