blob: 2667a5a49f27703e959a2077a24e037af710dac8 [file] [log] [blame]
/**
* 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.spifly.weaver;
import java.util.Arrays;
import java.util.HashSet;
import java.util.ServiceLoader;
import java.util.Set;
import org.apache.aries.spifly.Util;
import org.apache.aries.spifly.WeavingData;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
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 implements an ASM ClassVisitor which puts the appropriate ThreadContextClassloader
* calls around applicable method invocations. It does the actual bytecode weaving.
*/
public class TCCLSetterVisitor extends ClassVisitor implements Opcodes {
private static final Type CLASSLOADER_TYPE = Type.getType(ClassLoader.class);
private static final String GENERATED_METHOD_NAME = "$$FCCL$$";
private static final Type UTIL_CLASS = Type.getType(Util.class);
private static final Type CLASS_TYPE = Type.getType(Class.class);
private static final Type String_TYPE = Type.getType(String.class);
private final Type targetClass;
private final Set<WeavingData> weavingData;
// Set to true when the weaving code has changed the client such that an additional import
// (to the Util.class.getPackage()) is needed.
private boolean additionalImportRequired = false;
// This field is true when the class was woven
private boolean woven = false;
public TCCLSetterVisitor(ClassVisitor cv, String className, Set<WeavingData> weavingData) {
super(Opcodes.ASM5, cv);
this.targetClass = Type.getType("L" + className.replace('.', '/') + ";");
this.weavingData = weavingData;
}
public boolean isWoven() {
return woven;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new TCCLSetterMethodVisitor(mv, access, name, desc);
}
@Override
public void visitEnd() {
if (!woven) {
// if this class wasn't woven, then don't add the synthesized method either.
super.visitEnd();
return;
}
// Add generated static method
Set<String> methodNames = new HashSet<String>();
for (WeavingData wd : weavingData) {
/* Equivalent to:
* private static void $$FCCL$$<className>$<methodName>(Class<?> cls) {
* Util.fixContextClassLoader("java.util.ServiceLoader", "load", cls, WovenClass.class.getClassLoader());
* }
*/
String methodName = getGeneratedMethodName(wd);
if (methodNames.contains(methodName))
continue;
methodNames.add(methodName);
Method method = new Method(methodName, Type.VOID_TYPE, new Type[] {CLASS_TYPE});
GeneratorAdapter mv = new GeneratorAdapter(cv.visitMethod(ACC_PRIVATE + ACC_STATIC, methodName,
method.getDescriptor(), null, null), ACC_PRIVATE + ACC_STATIC, methodName,
method.getDescriptor());
//Load the strings, method parameter and target
mv.visitLdcInsn(wd.getClassName());
mv.visitLdcInsn(wd.getMethodName());
mv.loadArg(0);
mv.visitLdcInsn(targetClass);
//Change the class on the stack into a classloader
mv.invokeVirtual(CLASS_TYPE, new Method("getClassLoader",
CLASSLOADER_TYPE, new Type[0]));
//Call our util method
mv.invokeStatic(UTIL_CLASS, new Method("fixContextClassloader", Type.VOID_TYPE,
new Type[] {String_TYPE, String_TYPE, CLASS_TYPE, CLASSLOADER_TYPE}));
mv.returnValue();
mv.endMethod();
}
super.visitEnd();
}
private String getGeneratedMethodName(WeavingData wd) {
StringBuilder name = new StringBuilder(GENERATED_METHOD_NAME);
name.append(wd.getClassName().replace('.', '#'));
name.append("$");
name.append(wd.getMethodName());
if (wd.getArgClasses() != null) {
for (String cls : wd.getArgClasses()) {
name.append("$");
name.append(cls.replace('.', '#'));
}
}
return name.toString();
}
private class TCCLSetterMethodVisitor extends GeneratorAdapter {
Type lastLDCType;
public TCCLSetterMethodVisitor(MethodVisitor mv, int access, String name, String descriptor) {
super(Opcodes.ASM5, mv, access, name, descriptor);
}
/**
* Store the last LDC call. When ServiceLoader.load(Class cls) is called
* the last LDC call before the ServiceLoader.load() visitMethodInsn call
* contains the class being passed in. We need to pass this class to $$FCCL$$ as well
* so we can copy the value found in here.
*/
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
lastLDCType = ((Type) cst);
}
super.visitLdcInsn(cst);
}
/**
* Wrap selected method calls with
* Util.storeContextClassloader();
* $$FCCL$$(<class>)
* Util.restoreContextClassloader();
*/
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
WeavingData wd = findWeavingData(owner, name, desc);
if (opcode == INVOKESTATIC && wd != null) {
additionalImportRequired = true;
woven = true;
Label startTry = newLabel();
Label endTry = newLabel();
//start try block
visitTryCatchBlock(startTry, endTry, endTry, null);
mark(startTry);
// Add: Util.storeContextClassloader();
invokeStatic(UTIL_CLASS, new Method("storeContextClassloader", Type.VOID_TYPE, new Type[0]));
// Add: MyClass.$$FCCL$$<classname>$<methodname>(<class>);
if (ServiceLoader.class.getName().equals(wd.getClassName()) &&
"load".equals(wd.getMethodName()) &&
(wd.getArgClasses() == null || Arrays.equals(new String [] {Class.class.getName()}, wd.getArgClasses()))) {
// ServiceLoader.load() is a special case because it's a general-purpose service loader,
// therefore, the target class it the class being passed in to the ServiceLoader.load()
// call itself.
mv.visitLdcInsn(lastLDCType);
} else {
// In any other case, we're not dealing with a general-purpose service loader, but rather
// with a specific one, such as DocumentBuilderFactory.newInstance(). In that case the
// target class is the class that is being invoked on (i.e. DocumentBuilderFactory).
Type type = Type.getObjectType(owner);
mv.visitLdcInsn(type);
}
invokeStatic(targetClass, new Method(getGeneratedMethodName(wd),
Type.VOID_TYPE, new Type[] {CLASS_TYPE}));
//Call the original instruction
super.visitMethodInsn(opcode, owner, name, desc, itf);
//If no exception then go to the finally (finally blocks are a catch block with a jump)
Label afterCatch = newLabel();
goTo(afterCatch);
//start the catch
mark(endTry);
//Run the restore method then throw on the exception
invokeStatic(UTIL_CLASS, new Method("restoreContextClassloader", Type.VOID_TYPE, new Type[0]));
throwException();
//start the finally
mark(afterCatch);
//Run the restore and continue
invokeStatic(UTIL_CLASS, new Method("restoreContextClassloader", Type.VOID_TYPE, new Type[0]));
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
private WeavingData findWeavingData(String owner, String methodName, String methodDesc) {
owner = owner.replace('/', '.');
Type[] argTypes = Type.getArgumentTypes(methodDesc);
String [] argClassNames = new String[argTypes.length];
for (int i = 0; i < argTypes.length; i++) {
argClassNames[i] = argTypes[i].getClassName();
}
for (WeavingData wd : weavingData) {
if (wd.getClassName().equals(owner) &&
wd.getMethodName().equals(methodName) &&
(wd.getArgClasses() != null ? Arrays.equals(argClassNames, wd.getArgClasses()) : true)) {
return wd;
}
}
return null;
}
}
public boolean additionalImportRequired() {
return additionalImportRequired ;
}
}