/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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 org.apache.aries.proxy.impl.common; | |
import static java.lang.String.format; | |
import java.util.Map; | |
import java.util.Set; | |
import org.apache.aries.proxy.FinalModifierException; | |
import org.apache.aries.proxy.UnableToProxyException; | |
import org.objectweb.asm.AnnotationVisitor; | |
import org.objectweb.asm.Attribute; | |
import org.objectweb.asm.ClassReader; | |
import org.objectweb.asm.ClassVisitor; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Opcodes; | |
import org.objectweb.asm.Type; | |
import org.objectweb.asm.commons.GeneratorAdapter; | |
import org.objectweb.asm.commons.Method; | |
/** | |
* This class is used to copy concrete methods from a super-class into a sub-class, | |
* but then delegate up to the super-class implementation. We expect to be called | |
* with {@link ClassReader#SKIP_CODE}. This class is used when we can't weave | |
* all the way up the Class hierarchy and need to override methods on the first | |
* subclass we can weave. | |
*/ | |
final class MethodCopyingClassAdapter extends ClassVisitor implements Opcodes { | |
/** The super-class to copy from */ | |
private final Class<?> superToCopy; | |
/** Is the sub-class in the same package as the super */ | |
private final boolean samePackage; | |
/** The ASM {@link Type} of the sub-class */ | |
private final Type overridingClassType; | |
/** | |
* The Set of {@link Method}s that exist in the sub-class. This set must be | |
* live so modifications will be reflected in the parent and prevent clashes | |
*/ | |
private final Set<Method> knownMethods; | |
/** | |
* The map of field names to methods being added | |
*/ | |
private final Map<String, TypeMethod> transformedMethods; | |
private final AbstractWovenProxyAdapter wovenProxyAdapter; | |
public MethodCopyingClassAdapter(AbstractWovenProxyAdapter awpa, ClassLoader definingLoader, | |
Class<?> superToCopy, Type overridingClassType, Set<Method> knownMethods, | |
Map<String, TypeMethod> transformedMethods) { | |
super(Opcodes.ASM9); | |
this.wovenProxyAdapter = awpa; | |
this.superToCopy = superToCopy; | |
this.overridingClassType = overridingClassType; | |
this.knownMethods = knownMethods; | |
this.transformedMethods = transformedMethods; | |
//To be in the same package they must be loaded by the same classloader and be in the same package! | |
if(definingLoader != superToCopy.getClassLoader()) { | |
samePackage = false; | |
} else { | |
String overridingClassName = overridingClassType.getClassName(); | |
int lastIndex1 = superToCopy.getName().lastIndexOf('.'); | |
int lastIndex2 = overridingClassName.lastIndexOf('.'); | |
if(lastIndex1 != lastIndex2) { | |
samePackage = false; | |
} else if (lastIndex1 == -1) { | |
samePackage = true; | |
} else { | |
samePackage = superToCopy.getName().substring(0, lastIndex1) | |
.equals(overridingClassName.substring(0, lastIndex2)); | |
} | |
} | |
} | |
@Override | |
public final MethodVisitor visitMethod(final int access, String name, String desc, | |
String sig, String[] exceptions) { | |
MethodVisitor mv = null; | |
//As in WovenProxyAdapter, we only care about "real" methods, but also not | |
//abstract ones!. | |
if (!!!name.equals("<init>") && !!!name.equals("<clinit>") | |
&& (access & (ACC_STATIC | ACC_PRIVATE | ACC_SYNTHETIC | ACC_ABSTRACT | |
| ACC_NATIVE | ACC_BRIDGE)) == 0) { | |
// identify the target method parameters and return type | |
Method currentTransformMethod = new Method(name, desc); | |
// We don't want to duplicate a method we already overrode! | |
if(!!!knownMethods.add(currentTransformMethod)) | |
return null; | |
// found a method we should weave | |
// We can't override a final method | |
if((access & ACC_FINAL) != 0) | |
throw new RuntimeException(new FinalModifierException( | |
superToCopy, name)); | |
// We can't call up to a default access method if we aren't in the same | |
// package | |
if((access & (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE)) == 0) { | |
if(!!!samePackage) { | |
methodHiddenException(name); | |
} | |
} | |
//Safe to copy a call to this method! | |
Type superType = Type.getType(superToCopy); | |
// identify the target method parameters and return type | |
String methodStaticFieldName = "methodField" + AbstractWovenProxyAdapter.getSanitizedUUIDString(); | |
transformedMethods.put(methodStaticFieldName, new TypeMethod( | |
superType, currentTransformMethod)); | |
//Remember we need to copy the fake method *and* weave it, use a | |
//WovenProxyMethodAdapter as well as a CopyingMethodAdapter | |
MethodVisitor weaver = wovenProxyAdapter.getWeavingMethodVisitor( | |
access, name, desc, sig, exceptions, currentTransformMethod, | |
methodStaticFieldName, superType, false); | |
if(weaver instanceof AbstractWovenProxyMethodAdapter) { | |
//If we are weaving this method then we might have a problem. If it's a protected method and we | |
//aren't in the same package then we can't dispatch the call to another object. This may sound | |
//odd, but if class Super has a protected method foo(), then class Sub, that extends Super, cannot | |
//call ((Super)o).foo() in code (it can call super.foo()). If we are in the same package then this | |
//gets around the problem, but if not the class will fail verification. | |
if(!samePackage && (access & ACC_PROTECTED) != 0) { | |
methodHiddenException(name); | |
} | |
mv = new CopyingMethodAdapter((GeneratorAdapter) weaver, superType, currentTransformMethod); | |
} | |
else { | |
//For whatever reason we aren't weaving this method. The call to super.xxx() will always work | |
mv = new CopyingMethodAdapter(new GeneratorAdapter(access, currentTransformMethod, mv), | |
superType, currentTransformMethod); | |
} | |
} | |
return mv; | |
} | |
private void methodHiddenException(String name) { | |
String msg = format("The method %s in class %s cannot be called by %s because it is in a different package.", | |
name, superToCopy.getName(), overridingClassType.getClassName()); | |
throw new RuntimeException(msg, | |
new UnableToProxyException(superToCopy)); | |
} | |
/** | |
* This class is used to prevent any method body being copied, instead replacing | |
* the body with a call to the super-types implementation. The original annotations | |
* attributes etc are all copied. | |
*/ | |
private static final class CopyingMethodAdapter extends MethodVisitor { | |
/** The visitor to delegate to */ | |
private final GeneratorAdapter mv; | |
/** The type that declares this method (not the one that will override it) */ | |
private final Type superType; | |
/** The method we are weaving */ | |
private final Method currentTransformMethod; | |
public CopyingMethodAdapter(GeneratorAdapter mv, Type superType, | |
Method currentTransformMethod) { | |
super(Opcodes.ASM9); | |
this.mv = mv; | |
this.superType = superType; | |
this.currentTransformMethod = currentTransformMethod; | |
} | |
//TODO might not work for attributes | |
@Override | |
public final AnnotationVisitor visitAnnotation(String arg0, boolean arg1) { | |
return mv.visitAnnotation(arg0, arg1); | |
} | |
@Override | |
public final AnnotationVisitor visitAnnotationDefault() { | |
return mv.visitAnnotationDefault(); | |
} | |
@Override | |
public final AnnotationVisitor visitParameterAnnotation(int arg0, String arg1, | |
boolean arg2) { | |
return mv.visitParameterAnnotation(arg0, arg1, arg2); | |
} | |
@Override | |
public final void visitAttribute(Attribute attr) { | |
mv.visitAttribute(attr); | |
} | |
/** | |
* We skip code for speed when processing super-classes, this means we | |
* need to manually drive some methods here! | |
*/ | |
@Override | |
public final void visitEnd() { | |
mv.visitCode(); | |
//Equivalent to return super.method(args); | |
mv.loadThis(); | |
mv.loadArgs(); | |
mv.visitMethodInsn(INVOKESPECIAL, superType.getInternalName(), | |
currentTransformMethod.getName(), currentTransformMethod.getDescriptor()); | |
mv.returnValue(); | |
mv.visitMaxs(currentTransformMethod.getArgumentTypes().length + 1, 0); | |
mv.visitEnd(); | |
} | |
} | |
} |