/* * Copyright 2010-2012 VMware and contributors * * 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 org.springsource.loaded; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.logging.Logger; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.springsource.loaded.ConstantPoolChecker2.References; import org.springsource.loaded.Utils.ReturnType; import org.springsource.loaded.ri.ReflectiveInterceptor; /** * Rewrite method calls and field accesses. This is not only references to reloadable types but also calls to reflective APIs which * must be intercepted in case they refer to reloadable types at runtime. *
* The MethodInvokerRewriter actually manages a portion of the .slcache - it keeps track of two things: *
* Note: no caching is done here (the cache is not read or written to)
*
* @param typeRegistry the registry for which the rewriting is being done.
* @param bytes the bytes for the type to modify.
* @param skipReferencesCheck do we need to do a quick check to see if there is anything worth rewriting?
* @return the modified bytes.
*/
public static byte[] rewrite(TypeRegistry typeRegistry, byte[] bytes, boolean skipReferencesCheck) {
ensureCleanupDone();
return rewrite(false, typeRegistry, bytes, skipReferencesCheck);
}
public static byte[] rewrite(TypeRegistry typeRegistry, byte[] bytes) {
ensureCleanupDone();
return rewrite(false, typeRegistry, bytes, true);
}
private final static boolean DEBUG_CACHING;
static {
boolean b = false;
try {
b = System.getProperty("springloaded.debugcaching", "false").equalsIgnoreCase("true");
} catch (Exception e) {
}
DEBUG_CACHING = b;
}
public static byte[] rewriteUsingCache(String slashedClassName, TypeRegistry typeRegistry, byte[] bytes) {
ensureCacheIndexLoaded();
if (DEBUG_CACHING) {
System.out.println("cache check for " + slashedClassName);
}
if (slashedClassName != null) {
// Construct cachekey, something like: java/lang/String_3343
String cachekey = new StringBuilder(slashedClassName).append("_").append(bytes.length).toString();
Boolean b = cacheIndex.get(cachekey);
if (DEBUG_CACHING) {
System.out.println("was in index? " + b);
}
if (b != null) {
if (b.booleanValue()) { // the type was modified on an earlier run, there should be cached code around
String cacheFileName = new StringBuilder(slashedClassName.replace('/', '_')).append("_").append(bytes.length)
.append(".bytes").toString();
File cacheFile = new File(GlobalConfiguration.cacheDir, ".slcache" + File.separator + cacheFileName);
if (DEBUG_CACHING) {
System.out.println("Checking for cache file " + cacheFile);
}
if (cacheFile.exists()) {
// load the cached file
if (DEBUG_CACHING) {
System.out.println("loading and returning cached file contents");
}
try {
FileInputStream fis = new FileInputStream(cacheFile);
byte[] cachedBytes = Utils.loadBytesFromStream(fis);
return cachedBytes;
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
} else {
if (DEBUG_CACHING) {
System.out.println("returning unmodified bytes, no need to change");
}
// wasn't modified before, assume it isn't modified now either!
return bytes;
}
}
}
if (DEBUG_CACHING) {
System.out.println("modifying " + slashedClassName);
}
// the type has not been seen before or there was no cached file
return rewrite(true, typeRegistry, bytes, false);
}
private static void recursiveDelete(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
recursiveDelete(f);
}
}
}
boolean d = file.delete();
if (DEBUG_CACHING) {
System.out.println("Deleting " + file + " " + d);
}
}
private static void ensureCleanupDone() {
if (anyNecessaryCacheCleanupDone) {
return;
}
if (GlobalConfiguration.cleanCache) {
deleteCacheFiles();
}
anyNecessaryCacheCleanupDone = true;
}
private static void deleteCacheFiles() {
// Tidy up!
File cacheDir = new File(GlobalConfiguration.cacheDir, ".slcache");
if (cacheDir.exists()) {
recursiveDelete(cacheDir);
if (cacheIndex!=null) {
cacheIndex.clear();
}
}
versionInIndex=false;
}
private static final int CACHE_VERSION_1_1_0=1;
private static final int CACHE_VERSION_1_1_1=2;
private static final int CURRENT_CACHE_VERSION = CACHE_VERSION_1_1_1;
private static boolean versionInIndex = false;
/**
* Load the cache index from the file '<cacheDir>/.index'.
*
*/
private static void ensureCacheIndexLoaded() {
if (cacheIndex == null) {
cacheIndex = new HashMap
* Invokeinterface rewriting is done by calling the type registry to see if what we are about to do is OK. The method we
* call returns a boolean indicating whether it can be called directly or if we must direct it through the dynamic
* dispatch method.
*
*/
private void rewriteINVOKEINTERFACE(final int opcode, final String owner, final String name, final String desc,
boolean hasParams, ReturnType returnType, int classId) {
// 1. call 'boolean iicheck(classId|methodId, methodName+methodDescriptor)' to see if this needs interception
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name + desc);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeInterfaceName, "(ILjava/lang/String;)Z");
// 3. if false, do what was going to be done anyway
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1);
// 6. Package up any parameters
if (hasParams) {
Utils.collapseStackToArray(mv, desc);
}
// Prepare for the invocation:
if (!hasParams) {
// [targetInstance]
mv.visitInsn(DUP);
mv.visitInsn(ACONST_NULL); // no parameters
mv.visitInsn(SWAP); // [targetInstance NULL targetInstance]
} else {
// [targetInstance paramArray]
mv.visitInsn(SWAP);
mv.visitInsn(DUP_X1); // [targetInstance paramArray targetInstance]
}
mv.visitLdcInsn(name + desc); // [targetInstance paramArray targetInstance nameAndDescriptor]
// calling __execute(params array, this, name+desc)
mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor);
insertAppropriateReturn(returnType);
Label gotolabel = new Label();
mv.visitJumpInsn(GOTO, gotolabel);
mv.visitLabel(l1);
// do what we were going to do:
super.visitMethodInsn(opcode, owner, name, desc);
mv.visitLabel(gotolabel);
}
private void rewriteINVOKEVIRTUAL(final int opcode, final String owner, final String name, final String desc,
boolean hasParams, ReturnType returnType, int classId) {
// 1. call icheck(classId|methodId, methodName+methodDescriptor)
// to see if this needs interception
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name + desc);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeVirtualName, "(ILjava/lang/String;)Z");
// Return value is the extracted interface to call if there is a
// change and it can't be called directly
// 2. preserve a copy of the return value (new target)
// mv.visitInsn(DUP);
// 3. Was it null?
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1);
// 4. Not false
// 5. Store the target implementation of the interface that we
// will invoke later
// mv.visitVarInsn(ASTORE, max + 1);
// 6. Package up any parameters
if (hasParams) {
Utils.collapseStackToArray(mv, desc);
}
// Prepare for the invocation:
if (!hasParams) {
// [targetInstance]
mv.visitInsn(DUP);
mv.visitInsn(ACONST_NULL); // no parameters
mv.visitInsn(SWAP); // [targetInstance NULL targetInstance]
} else {
// [targetInstance paramArray]
mv.visitInsn(SWAP);
mv.visitInsn(DUP_X1); // [targetInstance paramArray
// targetInstance]
}
mv.visitLdcInsn(name + desc);
// calling __execute(params array,this,name+desc)
mv.visitMethodInsn(INVOKEVIRTUAL, owner, mDynamicDispatchName, mDynamicDispatchDescriptor);
insertAppropriateReturn(returnType);
Label gotolabel = new Label();
mv.visitJumpInsn(GOTO, gotolabel);
mv.visitLabel(l1);
// mv.visitInsn(POP);
// Here is where we end up if the test for changes failed (ie.
// there were no changes - just 'do what you were going to do'
super.visitMethodInsn(opcode, owner, name, desc);
mv.visitLabel(gotolabel);
}
/**
* Rewrite an INVOKESPECIAL that has been encountered in the code.
*
* The basic premise is then simple: call the TypeRegistry to check whether we can make the call we want to make. If we
* can then just do it, if we can't then that method will return a dispatcher instance that can handle the method so
* package up our parameters and invoke it.
*/
private void rewriteINVOKESPECIAL(final int opcode, final String owner, final String name, final String desc,
boolean hasParams, ReturnType returnType, int classId) {
if (unitializedObjectsCount == -1 && name.charAt(0) == '<') {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
if (!name.equals("
*
*/
private void rewritePUTFIELD(int opcode, String owner, String name, String desc) {
int classId = typeRegistry.getTypeIdFor(owner, true);
// Make a call to check if this field operation must be intercepted:
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mInstanceFieldInterceptionRequired, "(ILjava/lang/String;)Z");
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1
Utils.insertBoxInsns(mv, desc); // box the value if necessary
mv.visitInsn(SWAP);
mv.visitInsn(DUP_X1);
// now stack is: FieldAccessor|newValue|target
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESPECIAL, owner, mInstanceFieldSetterName,
"(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V");
Label l2 = new Label();
mv.visitJumpInsn(GOTO, l2);
mv.visitLabel(l1); // Did not need intercepting, do what you were going to do:
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitLabel(l2);
}
private void rewriteGETFIELD(int opcode, String owner, String name, String desc) {
// TODO [cglib optimizations] could recognize things that dont
// change in proxies
// if (name.equals("CGLIB$CALLBACK_0")) {
// super.visitFieldInsn(opcode, owner, name, desc);
// return;
// }
int classId = typeRegistry.getTypeIdFor(owner, true);
// Make a call to check if this field operation must be
// intercepted
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mInstanceFieldInterceptionRequired, "(ILjava/lang/String;)Z");
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1
mv.visitInsn(DUP);
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESPECIAL, owner, mInstanceFieldGetterName,
"(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");
if (desc.length() != 1) {
if (!desc.equals(jlObject)) {
mv.visitTypeInsn(CHECKCAST, toDescriptor(desc));
}
} else {
Utils.insertUnboxInsns(mv, desc.charAt(0), true);
}
Label l2 = new Label();
mv.visitJumpInsn(GOTO, l2);
mv.visitLabel(l1);
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitLabel(l2);
}
private void rewritePUTSTATIC(int opcode, String owner, String name, String desc) {
// TODO [perf] cache this information for 'us' so lookup not always necessary
int classId = typeRegistry.getTypeIdFor(owner, true);
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
// Make a call to check if this field operation must be intercepted:
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mStaticFieldInterceptionRequired, "(ILjava/lang/String;)Z");
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1
// top of heap will be the new value
Utils.insertBoxInsns(mv, desc);
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, owner, mStaticFieldSetterName, "(Ljava/lang/Object;Ljava/lang/String;)V");
Label l2 = new Label();
mv.visitJumpInsn(GOTO, l2);
mv.visitLabel(l1);
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitLabel(l2);
}
private void rewriteGETSTATIC(int opcode, String owner, String name, String desc) {
int classId = typeRegistry.getTypeIdFor(owner, true);
// Make a call to check if this field operation must be intercepted:
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mStaticFieldInterceptionRequired, "(ILjava/lang/String;)Z");
Label l1 = new Label();
mv.visitJumpInsn(IFEQ, l1); // IF (false) GOTO l1
// top of heap will be the new value
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKESTATIC, owner, mStaticFieldGetterName, "(Ljava/lang/String;)Ljava/lang/Object;");
if (desc.length() != 1) {
if (!desc.equals(jlObject)) {
mv.visitTypeInsn(CHECKCAST, toDescriptor(desc));
}
} else {
Utils.insertUnboxInsnsIfNecessary(mv, desc, true);
}
Label l2 = new Label();
mv.visitJumpInsn(GOTO, l2);
mv.visitLabel(l1);
super.visitFieldInsn(opcode, owner, name, desc);
mv.visitLabel(l2);
}
private String toDescriptor(String longDescriptor) {
if (longDescriptor.charAt(0) == '[') {
return longDescriptor;
}
return longDescriptor.substring(1, longDescriptor.length() - 1);
}
/**
* The big method for intercepting reflection. It is passed what the original code is trying to do (which method it is
* calling) and decides:
*
* boolean b = TypeRegistry.instanceFieldInterceptionRequired(regId|classId,name)
* if (b) {
* instance.r$set(newvalue,instance,name)
* } else {
* instance.name = newvalue
* }
*
*
*
*
* @return true if the call was modified/intercepted
*/
private boolean interceptReflection(String owner, String name, String desc) {
if (isInterceptable(owner, name)) {
//TODO: [...] this is probably a lot slower than unfolding this check into
// bunch of optimised if cases, but it is also much easier to manage.
// It should be possible to write something to generate the optimised
// if's from the contents of the 'interceptable' HashSet. Measure before optimizing.
callReflectiveInterceptor(owner, name, desc, mv);
return true;
}
return false;
}
int unitializedObjectsCount = 0;
@Override
public void visitTypeInsn(final int opcode, final String type) {
if (opcode == NEW) {
unitializedObjectsCount++;
}
super.visitTypeInsn(opcode, type);
}
private String toString(Handle handle) {
return "handle(tag="+handle.getTag()+",name="+handle.getName()+",desc="+handle.getDesc()+",owner="+handle.getOwner();
}
private String toString(Object[] oa) {
StringBuilder buf = new StringBuilder();
buf.append("[");
if (oa!=null) {
for (Object o:oa) {
buf.append(" ");
buf.append(o);
}
}
buf.append("]");
return buf.toString();
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, org.objectweb.asm.Handle bsm, Object... bsmArgs) {
int classId = typeRegistry.getTypeIdFor(slashedclassname, false);
if (classId==-1) {
throw new IllegalStateException();
}
// TODO *shudder* what about invoke dynamic calls that target reflective APIs
boolean handled = false;
// TODO Perhaps (for sake of my sanity initially) make a distinction here between the general invokedynamic case and the special lambda support case?
// name=m
// desc=()Lbasic/LambdaA2$Foo;
// bsm=handle(tag=6,
// name=metafactory,
// desc=(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;,
// owner=java/lang/invoke/LambdaMetafactory
// bsmArgs=[ ()I basic/LambdaA2.lambda$run$1()I (6) ()I]
if (bsm.getTag()==H_INVOKESTATIC) {
// InvokeDynamic(name=m,desc=()Lbasic/LambdaA$Foo;,bsm=handle(tag=6,name=metafactory,desc=(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;,owner=java/lang/invoke/LambdaMetafactory,bsmArgs=[ ()I basic/LambdaA.lambda$run$0()I (6) ()I]
System.out.println("InvokeDynamic(name="+name+",desc="+desc+",bsm="+toString(bsm)+",bsmArgs="+toString(bsmArgs));
// The other invokes use the 'owner' of the target method to determine which type registry should be part of this
// check. Here the 'owner' is wrapped up in the bootstrap method - as version 1 we can assume the owner is the lambdametafactory
// which *wont* be getting reloaded - so we already know we don't need to do some jiggery pokery.
// int classId = typeRegistry.getTypeIdFor(owner, true);
// Call type registry to determine 'can we do what we were going to do?'
// Stack parameters at callsite into object array
// The name and descriptor (desc) show what the parameters are on the stack
if (desc.charAt(1)==')') {
// no params
mv.visitInsn(ACONST_NULL);
}
else {
Utils.collapseStackToArray(mv, desc);
}
int bsmReferenceId = typeRegistry.recordBootstrapMethod(slashedclassname,bsm,bsmArgs);
// Method java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
mv.visitLdcInsn(typeRegistry.getId());
mv.visitLdcInsn(classId);
mv.visitMethodInsn(INVOKESTATIC,"java/lang/invoke/MethodHandles","lookup","()Ljava/lang/invoke/MethodHandles$Lookup;");
mv.visitLdcInsn(name+desc); // Ljava/lang/String;
mv.visitLdcInsn(bsmReferenceId); // I
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mPerformInvokeDynamicName, "([Ljava/lang/Object;IILjava/lang/Object;Ljava/lang/String;I)Ljava/lang/Object;");
handled=true;
// TODO handle return type
// mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(),classId));
// mv.visitLdcInsn(name+desc);
// mv.visitLdcInsn(BSM_NUMBER);
// mv.visitLdcInsn(bsmArgs);
// // What can we check to see whether it is necessary to intercept this call? Is it the return type of the descriptor? (For when
// // the bsm is recognizable as for lambda support)
// mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
// mv.visitLdcInsn(name + desc);
// mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeVirtualName, "(ILjava/lang/String;)Z");
// // Return value is the extracted interface to call if there is a
// // change and it can't be called directly
//
// // 2. preserve a copy of the return value (new target)
// // mv.visitInsn(DUP);
//
// // 3. Was it null?
// Label l1 = new Label();
// mv.visitJumpInsn(IFEQ, l1);
//
// // 4. Not false
//
// // 5. Store the target implementation of the interface that we
// // will invoke later
// // mv.visitVarInsn(ASTORE, max + 1);
//
// // 6. Package up any parameters
// if (hasParams) {
// Utils.collapseStackToArray(mv, desc);
// }
//
// // Prepare for the invocation:
// if (!hasParams) {
// // [targetInstance]
// mv.visitInsn(DUP);
// mv.visitInsn(ACONST_NULL); // no parameters
// mv.visitInsn(SWAP); // [targetInstance NULL targetInstance]
// } else {
// // [targetInstance paramArray]
// mv.visitInsn(SWAP);
// mv.visitInsn(DUP_X1); // [targetInstance paramArray
// // targetInstance]
// }
//
// mv.visitLdcInsn(name + desc);
//
// // calling __execute(params array,this,name+desc)
// mv.visitMethodInsn(INVOKEVIRTUAL, owner, mDynamicDispatchName, mDynamicDispatchDescriptor);
//
// insertAppropriateReturn(returnType);
// Label gotolabel = new Label();
// mv.visitJumpInsn(GOTO, gotolabel);
// mv.visitLabel(l1);
// // mv.visitInsn(POP);
// // Here is where we end up if the test for changes failed (ie.
// // there were no changes - just 'do what you were going to do'
// super.visitMethodInsn(opcode, owner, name, desc);
// mv.visitLabel(gotolabel);
}
else {
// TODO handle it!
}
if (!handled) {
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
}
}
@Override
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
if (GlobalConfiguration.interceptReflection && rewriteReflectiveCall(opcode, owner, name, desc)) {
return;
}
if (opcode == INVOKESPECIAL) {
unitializedObjectsCount--;
}
if (name.equals("$getCallSiteArray")) {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
// TODO [cglib optimizations] could recognize things that dont
// change in proxies
// if (name.equals("CGLIB$BIND_CALLBACKS")) {
// super.visitMethodInsn(opcode, owner, name, desc);
// return;
// }
boolean isReloadable = typeRegistry != null
&& (owner.equals(slashedclassname) ? thisClassIsReloadable : typeRegistry.isReloadableTypeName(owner));
if (!isReloadable) {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
rewroteOtherKindOfOperation = true;
boolean hasParams = desc.charAt(1) != ')';
ReturnType returnType = Utils.getReturnTypeDescriptor(desc);
// boolean isVoidReturn = returnType.isVoid();
int classId = typeRegistry.getTypeIdFor(owner, true);
if (opcode == INVOKESTATIC) {
rewriteINVOKESTATIC(opcode, owner, name, desc, hasParams, returnType, classId);
} else if (opcode == INVOKEINTERFACE) {
rewriteINVOKEINTERFACE(opcode, owner, name, desc, hasParams, returnType, classId);
} else if (opcode == INVOKEVIRTUAL) {
rewriteINVOKEVIRTUAL(opcode, owner, name, desc, hasParams, returnType, classId);
} else if (opcode == INVOKESPECIAL) {
rewriteINVOKESPECIAL(opcode, owner, name, desc, hasParams, returnType, classId);
} else {
Utils.logAndThrow(log, "Failed to rewrite instruction " + Utils.toOpcodeString(opcode) + " in method "
+ this.methodname);
}
}
/**
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
*
* @return true if the call was rewritten
*/
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
if (owner.length() > 10 && owner.charAt(0) == 'j'
&& (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
boolean rewritten = interceptReflection(owner, name, desc);
if (rewritten) {
return true;
}
// if (GlobalConfiguration.logNonInterceptedReflectiveCalls
// && !canIgnore(owner, name)) {
// // Only log those that are not intercepted
// if (GlobalConfiguration.logging &&
// log.isLoggable(Level.WARNING)) {
// log.log(Level.WARNING,
// "Reflection (not intercepted) from " + owner +
// " visitMethodInsn "
// + Utils.toOpcodeString(opcode) + " " + owner + " " + name
// + " " + desc);
// }
// }
}
return false;
}
/**
* Rewrite an INVOKESTATIC instruction.
*/
private void rewriteINVOKESTATIC(final int opcode, final String owner, final String name, final String desc,
boolean hasParams, ReturnType returnType, int classId) {
// 1. call istcheck(classId|methodId,
// methodName+methodDescriptor)
// If it returns 'null' then nothing has changed and the code
// can run as before. If it is not null
// then it is the instance of the extracted interface that
// should be called instead.
mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId));
mv.visitLdcInsn(name + desc);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeStaticName,
"(ILjava/lang/String;)Ljava/lang/Object;");
// 2. preserve a copy of the return value (new target)
mv.visitInsn(DUP);
// 3. Was it null?
Label l1 = new Label();
mv.visitJumpInsn(IFNULL, l1);
// 4. Not null, we need to dispatch to it
// 5. Store the target implementation of the interface that we will invoke later
mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(owner)); // TODO are checkcasts unnecessary sometimes? this one seems to be
mv.visitVarInsn(ASTORE, max + 1);
// 6. Package up any parameters
if (hasParams) {
Utils.collapseStackToArray(mv, desc);
}
// Prepare for the invocation:
if (!hasParams) {
mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance
mv.visitInsn(ACONST_NULL); // no parameters
mv.visitInsn(ACONST_NULL); // no instance, static method invocation
} else {
mv.visitVarInsn(ALOAD, max + 1); // dispatcher instance
mv.visitInsn(SWAP); // swap with that params array
mv.visitInsn(ACONST_NULL); // no instance, static method invocation
}
// TODO optimize to index, can we do that? is it worthwhile?
mv.visitLdcInsn(name + desc);
// 7. calling __execute(params array,this,name+desc)
mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(owner), mDynamicDispatchName, mDynamicDispatchDescriptor);
insertAppropriateReturn(returnType);
// 8. jump over the original call
Label gotolabel = new Label();
mv.visitJumpInsn(GOTO, gotolabel);
// 9. do what we were going to do
mv.visitLabel(l1);
mv.visitInsn(POP);
super.visitMethodInsn(opcode, owner, name, desc);
mv.visitLabel(gotolabel);
}
/**
* Based on the return type, insert the right return instructions. There will be an object on the stack when this method
* is called - the object must be either discarded (void), unboxed (primitive) or cast (reference) depending on the
* return type.
*/
private void insertAppropriateReturn(ReturnType returnType) {
if (returnType.isVoid()) {
mv.visitInsn(POP); // throw the result away (it was null)
} else {
if (returnType.isPrimitive()) {
Utils.insertUnboxInsnsIfNecessary(mv, returnType.descriptor, true);
} else {
mv.visitTypeInsn(CHECKCAST, returnType.descriptor);
}
}
}
/**
* All we need to do is know if the INVOKEINTERFACE that is about to run is OK to execute.
*