GITHUB-34: final methods, interfaces and enums

This commit is contained in:
Andy Clement
2014-02-03 20:31:21 -08:00
parent 10b95ef8c1
commit 59be31b9f9
18 changed files with 269 additions and 77 deletions

View File

@@ -109,7 +109,7 @@ public abstract class AbstractMember implements Constants {
return Modifier.isPublic(getModifiers());
}
public boolean isPrivateStaticFinal() {
public boolean isPrivateOrStaticOrFinal() {
return (modifiers & ACC_PRIVATE_STATIC_FINAL) != 0;
}

View File

@@ -36,6 +36,8 @@ import org.objectweb.asm.Opcodes;
*/
public class TypeDescriptorExtractor {
private final static boolean DEBUG_TYPE_DESCRIPTOR_EXTRACTOR = false;
private TypeRegistry registry;
public TypeDescriptorExtractor(TypeRegistry registry) {
@@ -50,7 +52,7 @@ public class TypeDescriptorExtractor {
}
/**
* Visit a class and accumulate enough information to build a TypeDescriptor.
* Visit a class and accumulate sufficient information to build a TypeDescriptor.
*/
class ExtractionVisitor implements ClassVisitor, Opcodes {
@@ -60,7 +62,9 @@ public class TypeDescriptorExtractor {
private String superclassName;
private String[] interfaceNames;
private boolean isGroovy = false;
private boolean isEnum = false;
private boolean hasClinit = false;
// TODO [perf - reduce garbage] make these collections lazily initialize
private List<MethodMember> constructors = new ArrayList<MethodMember>();
private List<MethodMember> methods = new ArrayList<MethodMember>();
private List<FieldMember> fieldsRequiringAccessors = new ArrayList<FieldMember>();
@@ -71,7 +75,6 @@ public class TypeDescriptorExtractor {
this.isReloadableType = isReloadableType;
}
public TypeDescriptor getTypeDescriptor() {
if (isReloadableType) {
computeCatchersAndSuperdispatchers();
@@ -87,7 +90,7 @@ public class TypeDescriptorExtractor {
}
/**
* Determine if there are clashes. A clash is where a static method takes the this reloadable type as its first parameter
* Determine if there are clashes. A clash is where a static method takes the reloadable type as its first parameter
* but in all other ways is the same as an existing instance method. For example this instance method A.foo(String) clashes
* with this static method A.foo(A, String). 'clashing' means the executor will have to do something to avoid a duplicate
* method problem and we'll have to differentiate between the two.
@@ -120,6 +123,7 @@ public class TypeDescriptorExtractor {
return registry.getDescriptorFor(slashedname);
}
// TODO [refactor] extract the type registry relationship code into a central helper class
private TypeDescriptor findTypeDescriptor(TypeRegistry registry, String typename) {
// follow the pattern for a classloader: recurse up trying to find it, then recurse down trying to load it
TypeRegistry regToTry = registry;
@@ -138,77 +142,92 @@ public class TypeDescriptorExtractor {
}
/**
* Create catcher methods for methods from our super-hierarchy that we don't yet override (but may after the initial define
* has happened).
* Algorithm: Go up the superclass hierarchy for a type and determine what should be caught in this type (see
* 'catchers' in notes.md). Methods that are private, static or final do *not* get a catcher. This method
* also computes superdispatchers - see 'superdispatchers' in notes.md
*
*/
private void walkHierarchyForCatchersAndSuperDispatchers(String superclass, List<String> superDispatchers, List<String> finalInHierarchy) {
TypeDescriptor supertypeDescriptor = superclass==null?null:findTypeDescriptor(registry, superclass);
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
System.out.println("Computing catchers on "+this.typename+" from superclass "+superclass);
}
boolean isReloadable = supertypeDescriptor.isReloadable();
for (MethodMember method: supertypeDescriptor.getMethods()) {
if (shouldCreateSuperDispatcherFor(method) && !superDispatchers.contains(method.nameAndDescriptor)) {
// need a public super dispatcher - so that we can reach that super method
// from a reloaded instance of this type
MethodMember superdispatcher = method.superDispatcherFor();
methods.add(superdispatcher);
superDispatchers.add(method.nameAndDescriptor);
}
if (shouldCatchMethod(method) && !finalInHierarchy.contains(method.getNameAndDescriptor())) {
// don't need the catcher if method is already defined since when the existing method is rewritten
// it will be kind of morphed into a catcher
// TODO what about a private method that is overridden by a static method (same name/descriptor but not
// an overrides relationship)
if (!isReloadable && Modifier.isFinal(method.getModifiers())) {
// Do not create a catcher, the supertype is not reloadable and so an implementation cannot be
// added lower in the type hierarchy
finalInHierarchy.add(method.getNameAndDescriptor());
continue;
}
MethodMember found = null;
for (MethodMember existingMethod : methods) {
if (existingMethod.equalsApartFromModifiers(method)) {
found = existingMethod;
break;
}
}
if (found != null) {
continue;
}
MethodMember catcherCopy = method.catcherCopyOf();
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
System.out.println("Adding catcher for "+method.nameAndDescriptor);
}
methods.add(catcherCopy);
} else {
if (method.isFinal()) {
finalInHierarchy.add(method.getNameAndDescriptor());
}
}
}
if (supertypeDescriptor.supertypeName != null) {
walkHierarchyForCatchersAndSuperDispatchers(supertypeDescriptor.supertypeName, superDispatchers, finalInHierarchy);
}
if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) {
// abstract class may be missing methods that it can implement from the interfaces
for (String interfaceName : supertypeDescriptor.superinterfaceNames) {
addCatchersForNonImplementedMethodsFrom(interfaceName, finalInHierarchy);
}
}
}
/**
* Compute and add the catch methods and super dispatch methods that apply to this type.
*/
private void computeCatchersAndSuperdispatchers() {
// When walking up the hierarchy we may hit a 'final' method which means we must not catch it.
// The 'shouldNotCatch' list stores things we discover like this that should not be caught
List<String> shouldNotCatch = new ArrayList<String>();
String type = superclassName;
// Don't need catchers in interfaces
if (Modifier.isInterface(this.flags)) {
if (Modifier.isInterface(this.flags)) { // Don't need catchers in interfaces
return;
}
List<String> superDispatcherAddedFor = new ArrayList<String>();
while (type != null) {
TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type);
// TODO review the need to create catchers for methods where the supertype is reloadable. In this situation we are already going to
// be intercepting the call side of these methods so we don't need the catcher. Could be a large performance increase and reduction in
// permgen, and simplification of stack traces
// if (!supertypeDescriptor.isReloadable()) {
for (MethodMember method : supertypeDescriptor.getMethods()) {
if (shouldCreateSuperDispatcherFor(method) && !superDispatcherAddedFor.contains(method.nameAndDescriptor)) {
// need a public super dispatcher - so that we can reach that super method
// from a reloaded instance of this type
MethodMember superdispatcher = method.superDispatcherFor();
methods.add(superdispatcher);
superDispatcherAddedFor.add(method.nameAndDescriptor);
}
if (shouldCatchMethod(method) && !shouldNotCatch.contains(method.getNameAndDescriptor())) {
// don't need the catcher if method is already defined since when the existing method is rewritten
// it will be kind of morphed into a catcher
// TODO what about a private method that is overridden by a static method (same name/descriptor but not
// an overrides relationship)
// if (supertypeDescriptor.isGroovyType() && !isGroovy) {
// if (method.getName().startsWith("super$")) {
// continue;
// }
// }
MethodMember found = null;
for (MethodMember existingMethod : methods) {
if (existingMethod.equalsApartFromModifiers(method)) {
found = existingMethod;
break;
}
}
if (found != null) {
continue;
}
MethodMember catcherCopy = method.catcherCopyOf();
// System.out.println("catcher is " + catcherCopy + " is groovy type? " + this.isGroovy);
methods.add(catcherCopy);
} else {
if (method.isFinal()) {
shouldNotCatch.add(method.getNameAndDescriptor());
}
}
}
// }
type = supertypeDescriptor.supertypeName;
}
// TODO [review design] review the need to create catchers for methods where the supertype is reloadable.
// Can we just add them to the topmost reloadable type?
List<String> doNotCatch = new ArrayList<String>();
walkHierarchyForCatchersAndSuperDispatchers(superclassName, new ArrayList<String>(), doNotCatch);
// ought to look in interfaces *if* we are an abstract class
if (Modifier.isAbstract(this.flags)/* && !Modifier.isInterface(this.flags)*/) {
// abstract class
// ought to look in interfaces if we are an abstract class
if (Modifier.isAbstract(this.flags) && !this.isEnum/* && !Modifier.isInterface(this.flags)*/) {
// abstract class may be missing methods that it can implement from the interfaces
for (String interfaceName : interfaceNames) {
addCatchersForNonImplementedMethodsFrom(interfaceName);
addCatchersForNonImplementedMethodsFrom(interfaceName, doNotCatch);
}
}
finalInHierarchy.addAll(shouldNotCatch);
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
System.out.println("For "+this.typename+" setting finalsInHierarchy to "+doNotCatch);
}
finalInHierarchy.addAll(doNotCatch);
}
// TODO should clone and finalize be in here?
@@ -218,8 +237,7 @@ public class TypeDescriptorExtractor {
(method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;")));
}
private void addCatchersForNonImplementedMethodsFrom(String interfacename) {
private void addCatchersForNonImplementedMethodsFrom(String interfacename,List<String> finalInNonReloadableType) {
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
for (MethodMember method : interfaceDescriptor.getMethods()) {
// If this class doesn't implement this interface method, add it
@@ -230,17 +248,21 @@ public class TypeDescriptorExtractor {
break;
}
}
if (!found) {
if (!found && !finalInNonReloadableType.contains(method.getNameAndDescriptor())) {
if (DEBUG_TYPE_DESCRIPTOR_EXTRACTOR) {
Log.log("adding catcher for ["+method+"] from ["+interfacename+"] to ["+this.typename+"]");
}
methods.add(method.catcherCopyOfWithAbstractRemoved());
}
}
for (String interfaceName : interfaceDescriptor.superinterfaceNames) {
addCatchersForNonImplementedMethodsFrom(interfaceName);
addCatchersForNonImplementedMethodsFrom(interfaceName,finalInNonReloadableType);
}
}
/**
* Field
* Protected fields in reloadable parents of a class need an accessor adding to the reloadable
* type so that the fields can be reached from the executor.
*/
private void computeFieldsRequiringAccessors() {
String type = superclassName;
@@ -274,13 +296,17 @@ public class TypeDescriptorExtractor {
* @return true if it should be caught
*/
private boolean shouldCatchMethod(MethodMember method) {
return !(method.isPrivateStaticFinal() || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
return !(method.isPrivateOrStaticOrFinal() || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) ||
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
}
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
this.flags = flags;
this.superclassName = superclassName;
this.interfaceNames = interfaceNames;
if (superclassName!=null && superclassName.equals("java/lang/Enum")) {
this.isEnum = true;
}
this.typename = name;
}
@@ -304,7 +330,7 @@ public class TypeDescriptorExtractor {
}
return null;
}
// For each method, copy it into the new class making appropriate adjustments
/**
* Visit a method in the class and build an appropriate representation for it to include in the extracted output.
*/