blob: 762c4228ed8f5a8b0568c57bd7fef30ef688b5c8 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.javac;
import com.google.gwt.dev.util.Util;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Creates string hashes for various purposes from walking bytecode.
*/
public class BytecodeSignatureMaker {
/**
* This visitor looks at methods and members to compute a signature. This is
* intended for determining if a type needs to be recompiled if byte code it
* depends on changes.
*
* At first, you'd think only public and protected members should be
* considered, but the JSNI violator pattern means that even a change in a
* private member might invalidate an access from another class.
*/
private static class CompileDependencyVisitor extends ClassVisitor {
/**
* Mask to strip access bits we don't care about for computing the
* signature.
*/
private static final int ACCESS_FILTER_MASK =
~(Opcodes.ACC_DEPRECATED | Opcodes.ACC_NATIVE | Opcodes.ACC_STRICT
| Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_SUPER | Opcodes.ACC_TRANSIENT | Opcodes.ACC_VOLATILE);
private String header;
private Map<String, String> fields = new HashMap<String, String>();
private Map<String, String> methods = new HashMap<String, String>();
public CompileDependencyVisitor() {
super(Opcodes.ASM7);
}
public String getSignature() {
return Util.computeStrongName(Util.getBytes(getRawString()));
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
StringBuilder headerBuilder = new StringBuilder();
// ignoring version
headerBuilder.append(access & ACCESS_FILTER_MASK);
headerBuilder.append(":");
headerBuilder.append(name);
if (signature != null) {
headerBuilder.append(":");
headerBuilder.append(signature);
}
if (superName != null) {
headerBuilder.append(":");
headerBuilder.append(superName);
}
if (interfaces != null) {
Arrays.sort(interfaces);
for (String iface : interfaces) {
headerBuilder.append(":");
headerBuilder.append(iface);
}
}
header = headerBuilder.toString();
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// ignore
return null;
}
@Override
public void visitAttribute(Attribute attr) {
// ignore
}
@Override
public void visitEnd() {
// unused
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
Object value) {
StringBuilder fieldBuilder = new StringBuilder();
// We don't care about synthetic fields
if ((access & (Opcodes.ACC_SYNTHETIC)) == 0) {
fieldBuilder.append(access & ACCESS_FILTER_MASK);
fieldBuilder.append(":");
fieldBuilder.append(name);
fieldBuilder.append(":");
fieldBuilder.append(desc);
if (signature != null) {
fieldBuilder.append(":");
fieldBuilder.append(signature);
}
if (value != null) {
fieldBuilder.append(":");
fieldBuilder.append(value.toString());
}
fields.put(name, fieldBuilder.toString());
}
// ignoring annotations/attributes on the field.
return null;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// ignored
}
@Override
public org.objectweb.asm.MethodVisitor visitMethod(int access, String name, String desc,
String signature,
String[] exceptions) {
// We don't care about synthetic methods
if ((access & (Opcodes.ACC_SYNTHETIC)) == 0) {
StringBuilder methodBuilder = new StringBuilder();
methodBuilder.append(access & ACCESS_FILTER_MASK);
methodBuilder.append(":");
methodBuilder.append(name);
methodBuilder.append(":");
methodBuilder.append(desc);
if (signature != null) {
methodBuilder.append(":");
methodBuilder.append(signature);
}
if (exceptions != null) {
String[] sortedExceptions = exceptions;
Arrays.sort(sortedExceptions);
for (String exception : sortedExceptions) {
methodBuilder.append(":");
methodBuilder.append(exception);
}
}
methods.put(name, methodBuilder.toString());
}
return null;
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
// ignored
}
@Override
public void visitSource(String source, String debug) {
// ignore
}
private String getRawString() {
StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(header);
signatureBuilder.append("|");
// sort all fields and methods for a deterministic signature.
String[] sortedFields = fields.values().toArray(new String[0]);
Arrays.sort(sortedFields);
for (String field : sortedFields) {
signatureBuilder.append(field);
signatureBuilder.append("|");
}
String[] sortedMethods = methods.values().toArray(new String[0]);
Arrays.sort(sortedMethods);
for (String method : sortedMethods) {
signatureBuilder.append(method);
signatureBuilder.append("|");
}
return signatureBuilder.toString();
}
}
/**
* Returns a hash computed from the non-private/non-synthetic members and
* methods in a class.
*
* @param byteCode byte code for class to analyze.
* @return a hex string representing an MD5 digest.
*/
public static String getCompileDependencySignature(byte[] byteCode) {
CompileDependencyVisitor v = visitCompileDependenciesInBytecode(byteCode);
return v.getSignature();
}
/**
* Returns a raw string used to compute the hash from the
* non-synthetic members and methods in a class.
*
* @param byteCode byte code for class to analyze.
* @return a human readable string of all public API fields
*/
static String getCompileDependencyRawSignature(byte[] byteCode) {
CompileDependencyVisitor v = visitCompileDependenciesInBytecode(byteCode);
return v.getRawString();
}
private static CompileDependencyVisitor visitCompileDependenciesInBytecode(byte[] byteCode) {
ClassReader reader = new ClassReader(byteCode);
CompileDependencyVisitor v = new CompileDependencyVisitor();
reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return v;
}
private BytecodeSignatureMaker() {
}
}