Java ASM Bytecode Modification-Changing method bodies - java

I have a method of a class in a jar whose body I want to exchange with my own. In this case I just want to have the method print out "GOT IT" to the console and return true;
I am using the system loader to load the classes of the jar. I am using reflection to make the system classloader be able to load classes by bytecode. This part seems to be working correctly.
I am following the method replacement example found here: asm.ow2.org/current/asm-transformations.pdf.
My code is as follows:
public class Main
{
public static void main(String[] args)
{
URL[] url = new URL[1];
try
{
url[0] = new URL("file:////C://Users//emist//workspace//tmloader//bin//runtime//tmgames.jar");
verifyValidPath(url[0]);
}
catch (Exception ex)
{
System.out.println("URL error");
}
Loader l = new Loader();
l.loadobjection(url);
}
public static void verifyValidPath(URL url) throws FileNotFoundException
{
File filePath = new File(url.getFile());
if (!filePath.exists())
{
throw new FileNotFoundException(filePath.getPath());
}
}
}
class Loader
{
private static final Class[] parameters = new Class[] {URL.class};
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try
{
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
}
catch (Throwable t)
{
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
private Class loadClass(byte[] b, String name)
{
//override classDefine (as it is protected) and define the class.
Class clazz = null;
try
{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try
{
Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
}
finally
{
method.setAccessible(false);
}
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return clazz;
}
public void loadobjection(URL[] myJar)
{
try
{
Loader.addURL(myJar[0]);
//tmcore.game is the class that holds the main method in the jar
/*
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
*/
MethodReplacer mr = null;
ClassReader cr = new ClassReader("tmcore.objwin");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodVisitor mv = null;
try
{
mr = new MethodReplacer(cw, "Test", "(Ljava/lang/String;ZLjava/lang/String;)Z");
}
catch (Exception e)
{
System.out.println("Method Replacer Exception");
}
cr.accept(mr, ClassReader.EXPAND_FRAMES);
PrintWriter pw = new PrintWriter(System.out);
loadClass(cw.toByteArray(), "tmcore.objwin");
Class<?> classToLoad = Class.forName("tmcore.game", true, this.getClass().getClassLoader());
if(classToLoad == null)
{
System.out.println("No tmcore.game");
return;
}
//game doesn't have a default constructor, so we need to get the reference to public game(String[] args)
Constructor ctor = classToLoad.getDeclaredConstructor(String[].class);
if(ctor == null)
{
System.out.println("can't find constructor");
return;
}
//Instantiate the class by calling the constructor
String[] args = {"tmgames.jar"};
Object instance = ctor.newInstance(new Object[]{args});
if(instance == null)
{
System.out.println("Can't instantiate constructor");
}
//get reference to main(String[] args)
Method method = classToLoad.getDeclaredMethod("main", String[].class);
//call the main method
method.invoke(instance);
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
}
}
}
public class MethodReplacer extends ClassVisitor implements Opcodes
{
private String mname;
private String mdesc;
private String cname;
public MethodReplacer(ClassVisitor cv, String mname, String mdesc)
{
super(Opcodes.ASM4, cv);
this.mname = mname;
this.mdesc = mdesc;
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces)
{
this.cname = name;
cv.visit(version, access, name, signature, superName, interfaces);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions)
{
String newName = name;
if(name.equals(mname) && desc.equals(mdesc))
{
newName = "orig$" + name;
generateNewBody(access, desc, signature, exceptions, name, newName);
System.out.println("Replacing");
}
return super.visitMethod(access, newName, desc, signature, exceptions);
}
private void generateNewBody(int access, String desc, String signature, String[] exceptions,
String name, String newName)
{
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(access, cname, newName, desc);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
The problem seems to be at mv.visitMethodInsn(access, cname, newName, desc); in generateMethodBody inside MethodReplacer.
I get an "Illegal Type in constant pool" error.
I'm not sure what I'm missing...but after reading and testing for about 3 days I'm still not getting anywhere.
[Edit]
In case you were wondering, tmcore is a single player "Objection" game for lawyers. I'm doing this for the fun of it. The program successfully launches the game and everything is fine, removing the modifications from MethodReplacer makes the game behave as designed. So the issue seems to be isolated to bad bytecode/modifications by me inside the method replacer.
[EDIT2]
CheckClassAdapter.verify(cr, true, pw); returns the exact same bytecode that the function is supposed to have before editing. It is as if the changes are not being done.
[EDIT3]
copy of classtoload commented out as per comments

If you are using Eclipse, you should install Bytecode Outline - it is indispensible.
I built a small test for what you want to achieve (this should match the signature of your test method, you will have to change package and classname):
package checkASM;
public class MethodCall {
public boolean Test(String a, boolean b, String c) {
System.out.println("GOTit");
return false;
}
}
requires the following bytecode to build the method:
{
mv = cw.visitMethod(ACC_PUBLIC, "Test",
"(Ljava/lang/String;ZLjava/lang/String;)Z", null, null);
mv.visitCode();
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System",
"out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("GOTit");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable("this", "LcheckASM/MethodCall;", null, l1, l3, 0);
mv.visitLocalVariable("a", "Ljava/lang/String;", null, l1, l3, 1);
mv.visitLocalVariable("b", "Z", null, l1, l3, 2);
mv.visitLocalVariable("c", "Ljava/lang/String;", null, l1, l3, 3);
mv.visitMaxs(4, 4);
mv.visitEnd();
}
Calls to visitLineNumber can be omitted. So apparently, you are missing all labels, forgot to load the method parameters, did not ignore the return value, set the wrong values for visitMaxs (this is not necessarily needed, it depends on your ClassWriter flags if I recall correctly) and did not visit local variables (or parameters in this case).
Additionally, your classloading seems to be a little confused / messed up.
I don't have the jar (so I can't say if these work), but maybe you could replace Main and Loader:
Main:
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
public class Main {
public static void main(String[] args) {
try {
Loader.instrumentTmcore(args);
} catch (Exception e) {
System.err.println("Ooops");
e.printStackTrace();
}
}
}
Loader:
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
public class Loader {
public static ClassReader fetchReader(String binaryName) throws Exception {
return new ClassReader(
Loader.class.getClassLoader().getSystemResourceAsStream(
binaryName.replace('.', '/') + ".class"
)
)
;
}
public static synchronized Class<?> loadClass(byte[] bytecode)
throws Exception {
ClassLoader scl = ClassLoader.getSystemClassLoader();
Class<?>[] types = new Class<?>[] {
String.class, byte[].class, int.class, int.class
};
Object[] args = new Object[] {
null, bytecode, 0, bytecode.length
};
Method m = ClassLoader.class.getMethod("defineClass", types);
m.setAccessible(true);
return (Class<?>) m.invoke(scl, args);
}
public static void instrumentTmcore(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodReplacer mr = new MethodReplacer(cw, "Test",
"(Ljava/lang/String;ZLjava/lang/String;)Z");
fetchReader("tmcore.objwin").accept(mr, ClassReader.EXPAND_FRAMES);
loadClass(cw.toByteArray());
Class.forName("tmcore.game")
.getMethod("main", new Class<?>[] {args.getClass()})
.invoke(null, new Object[] { args });
}
}

ASKER'S ANSWER MOVED FROM QUESTION
The java bytecode was never the problem. It is the way I was loading the jar which made it impossible to instrument the code.
Thanks to Ame for helping me tackle it.
The following code works:
MAIN
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.io.FileInputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public class Main implements Opcodes
{
public static void main(String[] args) throws Exception
{
byte[] obj = readClass("tmcore/obj.class");
ClassReader objReader = new ClassReader(obj);
ClassWriter objWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
MethodReplacer demoReplacer = new MethodReplacer(objWriter, "run", "()V");
demoReplacer.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "tmcore/obj", null, "java/applet/Applet", new String[] { "java/lang/Runnable" });
objReader.accept(demoReplacer, ClassReader.EXPAND_FRAMES);
objReader = new ClassReader(objWriter.toByteArray());
Class objC = Loader.loadClass(objWriter.toByteArray(), "tmcore.obj");
if(objC == null)
{
System.out.println("obj cannot be loaded");
}
Class game = ClassLoader.getSystemClassLoader().loadClass("tmcore.game");
if(game == null)
{
System.out.println("Can't load game");
return;
}
Constructor ctor = game.getDeclaredConstructor(String[].class);
if(ctor == null)
{
System.out.println("can't find constructor");
return;
}
//Instantiate the class by calling the constructor
String[] arg = {"tmgames.jar"};
Object instance = ctor.newInstance(new Object[]{args});
if(instance == null)
{
System.out.println("Can't instantiate constructor");
}
//get reference to main(String[] args)
Method method = game.getDeclaredMethod("main", String[].class);
//call the main method
method.invoke(instance);
}
public static void verifyValidPath(String path) throws FileNotFoundException
{
File filePath = new File(path);
if (!filePath.exists())
{
throw new FileNotFoundException(filePath.getPath());
}
}
public static byte[] readClass(String classpath) throws Exception
{
verifyValidPath(classpath);
File f = new File(classpath);
FileInputStream file = new FileInputStream(f);
if(file == null)
throw new FileNotFoundException();
byte[] classbyte = new byte[(int)f.length()];
int offset = 0, numRead = 0;
while (offset < classbyte.length
&& (numRead=file.read(classbyte, offset, classbyte.length-offset)) >= 0)
{
offset += numRead;
}
if (offset < classbyte.length)
{
file.close();
throw new IOException("Could not completely read file ");
}
file.close();
return classbyte;
}
}
LOADER:
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
class Loader
{
private static final Class[] parameters = new Class[] {URL.class};
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try
{
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
}
catch (Throwable t)
{
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
public static Class loadClass(byte[] b, String name)
{
//override classDefine (as it is protected) and define the class.
Class clazz = null;
try
{
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
// protected method invocaton
method.setAccessible(true);
try
{
Object[] args = new Object[] {name, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
}
finally
{
method.setAccessible(false);
}
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
return clazz;
}
}
MethodReplacer remains the same.

Related

How to intercept inherited method

I want to build a Java agent with Byte Buddy, which intercepts JDBC connection creation and closing event. I want to support data sources such as HikariCP, DBCP, etc, also plain JDBC with RDBMS drivers.
My code:
Instrumention setup
new AgentBuilder.Default()
.type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(not(isAbstract())))
.transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);
Intercepting code
package org.wxt.xtools.agents.jdbcmon;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.wxt.xtools.agents.utils.StringUtils;
import org.wxt.xtools.agents.utils.TopN;
import ch.qos.logback.classic.Logger;
import io.prometheus.client.Histogram;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
/**
*
* #author ggfan
*
*/
public class JDBCAPIInterceptor {
// ***ATTENTION*** fields below must be public, as we will access them from the
// intercepting methods
public static Map<Integer, ConnectionInfo> conns = new HashMap<Integer, ConnectionInfo>();
public static Map<String, String> stacks = new HashMap<String, String>();
public static Map<String, Integer> counstsByStack = new HashMap<String, Integer>();
public static TopN<StatementExecutionInfo> slowStatements = new TopN<StatementExecutionInfo>(10, new Comparator<StatementExecutionInfo>() {
#Override
public int compare(StatementExecutionInfo o1, StatementExecutionInfo o2) {
long d1 = o1.getExecutionEndTime() - o1.getExecutionStartTime();
long d2 = o2.getExecutionEndTime() - o2.getExecutionStartTime();
return Long.compare(d1, d2);
}
});
public static Logger log = JDBCMonitor.log;
public static JDBCStatsReport getInfo() {
JDBCStatsReport report = new JDBCStatsReport();
List<ConnectionInfo> data = new ArrayList<ConnectionInfo>();
for (ConnectionInfo ci : conns.values()) {
ConnectionInfo copy = new ConnectionInfo();
copy.setHash(ci.getHash());
copy.setStackHash(ci.getStackHash() + "(" + counstsByStack.get(ci.getStackHash()) + ")");
copy.setCreationTime(ci.getCreationTime());
copy.setLastActiveTime(ci.getLastActiveTime());
copy.setLastMethod(ci.getLastMethod());
copy.setCurrentStatement(ci.getCurrentStatement());
data.add(copy);
}
report.setConnectionInfo(data);
report.setSlowStatementTopN(slowStatements);
return report;
}
#RuntimeType
public static Object interceptor(#Origin Class<?> clazz, #Origin Method method, #SuperCall Callable<?> callable,
#net.bytebuddy.implementation.bind.annotation.This Object inst, #AllArguments Object[] args)
throws Exception {
// this is equal to original method, will not cause inner calls to other
// matching methods get intercepted.
// Object o = callable.call();
log.debug("intercepting {}#{}", clazz.getName(), method.getName());
int hashCode = System.identityHashCode(inst);
if (java.sql.Connection.class.isAssignableFrom(clazz)) {
ConnectionInfo ci = conns.get(hashCode);
// TODO fix bug here!!!
if (ci == null) {
log.debug("connection#{} is not in records anymore, maybe called #{} after close, ignored", hashCode,
method.getName());
}
return interceptConnectionMethods(method, hashCode, ci, callable, args);
} else if (java.sql.Statement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
} else if (java.sql.PreparedStatement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
} else if (java.sql.CallableStatement.class.isAssignableFrom(clazz)) {
return interceptStatementMethods(method, hashCode, callable, args);
}
return null;
}
private static Object interceptConnectionMethods(Method method, int hashCode, ConnectionInfo ci,
Callable<?> callable, Object[] args) throws Exception {
Object o = callable.call();
log.debug("connection#{} used by {}", hashCode, method.getName());
ci.setLastActiveTime(System.currentTimeMillis());
ci.setLastMethod(method.getName());
int resultHash = System.identityHashCode(o);
if (method.getName().equals("close")) {
log.info("connection#{} released", hashCode);
String stackHash = ci.getStackHash();
Integer scount = counstsByStack.get(stackHash);
if (scount != null && scount > 0) {
int newscount = scount - 1;
log.info("set connection count to {} by stack hash {}", newscount, stackHash);
if (newscount == 0) {
counstsByStack.remove(stackHash);
stacks.remove(stackHash);
} else {
counstsByStack.put(stackHash, newscount);
}
} else {
log.error("connection count by stack hash {} is not supposed to be null or less than zero", stackHash);
}
conns.remove(hashCode);
} else if (method.getName().equals("createStatement")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.NORMAL);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.NORMAL, hashCode);
} else if (method.getName().equals("prepareStatement")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.PREPARED);
stmt.setSqlText((String) args[0]);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.PREPARED, hashCode);
} else if (method.getName().equals("prepareCall")) {
StatementExecutionInfo stmt = new StatementExecutionInfo();
stmt.setType(StatementType.CALLABLE);
stmt.setSqlText((String) args[0]);
stmt.setHash(resultHash);
conns.get(hashCode).setCurrentStatement(stmt);
log.info("statement#{} created, type {}, attached to connection#{}", resultHash, StatementType.CALLABLE, hashCode);
}
return o;
}
private static Object interceptStatementMethods(Method method, int hashCode, Callable<?> callable, Object[] args)
throws Exception {
log.info("intercepting statement method {}", method.getName());
Object o = null;
ConnectionInfo containingCI = conns.values().stream().filter(c -> c.getCurrentStatement().getHash() == hashCode)
.findFirst().orElse(null);
if (containingCI == null) {
log.warn("unexpected situation happened: statement can't found containing connection!");
}
if (method.getName().equals("close")) {
o = callable.call();
// TODO statement close method not intercepted ??
if (containingCI != null) {
containingCI.setCurrentStatement(null);
log.info("statement#{} closed, detached from connection#{}", hashCode, containingCI.getHash());
}
}
// all statement execution method trigger
else if (method.getName().startsWith("execute")) {
String stack = getCurrentThreadStackTrace();
String stackHash = StringUtils.md5(stack);
stacks.put(stackHash, stack);
log.info("statement#{} {} started by {}", hashCode, method.getName(), stackHash);
long est = System.currentTimeMillis();
if (containingCI != null) {
containingCI.getCurrentStatement().setExecutionStartTime(est);
containingCI.getCurrentStatement().setStackHash(stackHash);
containingCI.getCurrentStatement().setExecutionCallStack(stack);
if (args.length > 0) {
containingCI.getCurrentStatement().setSqlText((String) args[0]);
}
}
Histogram.Timer timer = JDBCMetrics.SQL_EXECUTION_TIME.startTimer();
try {
o = callable.call();
} finally {
timer.observeDuration();
}
long eet = System.currentTimeMillis();
log.info("statement#{} {} finished, duration is {}", hashCode, method.getName(), (eet - est));
if (containingCI != null) {
containingCI.getCurrentStatement().setExecutionEndTime(eet);
slowStatements.add(containingCI.getCurrentStatement());
}
}
return o;
}
public static String getCurrentThreadStackTrace() throws IOException {
StringWriter sw = new StringWriter();
Throwable t = new Throwable("");
t.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
sw.close();
return stackTrace;
}
#Advice.OnMethodExit
public static void intercept(#net.bytebuddy.asm.Advice.Origin Constructor<?> m, #This Object inst)
throws Exception {
log.debug("----------------- constructor intercept -------------------------");
if (java.sql.Connection.class.isAssignableFrom(m.getDeclaringClass())) {
// some CP library override hashCode method
int hashCode = System.identityHashCode(inst);
ConnectionInfo ci = new ConnectionInfo();
ci.setHash(hashCode);
ci.setCreationTime(System.currentTimeMillis());
String stackTrace = getCurrentThreadStackTrace();
String shash = StringUtils.md5(stackTrace);
stacks.put(shash, stackTrace);
// ci.setStack(stackTrace);
ci.setStackHash(shash);
log.info("connection#{} acquired by stack#{}", hashCode, shash);
log.debug(stackTrace);
conns.put(hashCode, ci);
Integer scount = counstsByStack.get(ci.getStackHash());
if (scount == null) {
counstsByStack.put(ci.getStackHash(), 1);
} else {
counstsByStack.put(ci.getStackHash(), scount + 1);
}
}
}
}
However, this will not work for some cases. Take HikariCP for example:
HikariCP's implementation has an abstract class ProxyConnection, which has the close method implemented, then a class HikariProxyConnection extends ProxyConnection, which overides some methods, except close. If I setup instrumentation on HikariProxyConnection, the close method will not be intercepted. If I change my code to:
new AgentBuilder.Default()
.type(startMatcher.and(isSubTypeOf(java.sql.Connection.class).and(isAbstract()))
.transform(constructorTransformer).transform(methodsTransformer).with(listener).installOn(inst);
it will work for HikariCP, but not for other connection pool implementations.
For my use case, is there a unified way? No matter what connection pool is used, and even with plain JDBC.
Your matcher:
isSubTypeOf(java.sql.Connection.class).and(not(isAbstract())
explicitly excludes abstract classes. You would need to instrument all methods to achieve what you wanted if you are using Advice.
Also, it seems like you are blending concepts:
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.This;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
The latter annotations belong to MethodDelegation, not to Advice. Refer to the javadoc to see how advice should be applied.

Java CompilationTask/ClassLoader from user define source failed to "call"

I'm trying the sample code from [Core java programming] volume 2, chapter 8, with tiny modification. First. I've build a intellij project, with a simple class and "f()" function, already compiled:
public class BasicTypes {
public void f() {
System.out.println("BasicTypes.f()");
}
}
Then I have 2 classes to load class:
class StringBuilderJavaSource extends SimpleJavaFileObject {
private StringBuilder code;
public StringBuilderJavaSource(String className) {
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension),
Kind.SOURCE);
code = new StringBuilder();
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
System.out.println(code);
return code;
}
public void append(String s) {
code.append(s);
code.append('\n');
}
}
class ByteArrayJavaClass extends SimpleJavaFileObject {
private ByteArrayOutputStream stream;
public ByteArrayJavaClass(String className) {
super(URI.create("bytes:///" + className), Kind.CLASS);
stream = new ByteArrayOutputStream();
}
#Override
public OutputStream openOutputStream() {
return stream;
}
public byte[] getBytes() {
return stream.toByteArray();
}
}
And then the main function:
public static void main(String args[]) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final List<ByteArrayJavaClass> classFileObjects = new ArrayList<>();
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
JavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null);
fileManager = new ForwardingJavaFileManager<JavaFileManager>(fileManager) {
#Override
public JavaFileObject getJavaFileForOutput(
Location location,
final String className,
JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
System.out.println("Enter getJavaFileForOutput");
if (className.startsWith("x.")) {
ByteArrayJavaClass fileObject = new ByteArrayJavaClass(className);
classFileObjects.add(fileObject);
System.out.println("Enter x");
return fileObject;
} else {
System.out.println("Enter others");
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
};
StringBuilderJavaSource source = new StringBuilderJavaSource("myFirstClass");
source.append("package x;");
source.append("public class myFirstClass extends " + "BasicTypes" + " {");
source.append(" public void f(){System.out.println(\"myFirstClass.f()\");}");
source.append("}");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector,
null, null, Arrays.asList(source));
Boolean result = task.call();
if (!result) {
System.out.println("Compilation failed");
System.exit(1);
}
}
The program fails to compile and prints:
package x;
public class myFirstClass extends BasicTypes {
public void f(){System.out.println("myFirstClass.f()");}
}
Compilation failed
Seems the failure doesn't have enough information for me to do tourble-shooting.
So where did I get wrong in my program, and how to fix it?
Thanks a lot.

VerifyException when adding an if stmt to a method at runtime

Let's say I have a simple
class C$Manipulatables{
public Wrapper get(long v, String f){
if(v == 0){
return new Wrapper(0);
}else{
throw new RuntimeException("unacceptable: v not found");
}
}
}
I now want to redefine the get in C$Manipulatables, s.t. it reads
class C$Manipulatables{
public Wrapper get(long v, String f){
if(v == 1){ return null; }
if(v == 0){
return new Wrapper(0);
}else{
throw new RuntimeException("unacceptable: v not found");
}
}
}
This will trigger a nullpointer exception if I try to use the new value, but that's ok - for now I just want confirmation that the new code is loaded.
We add the code with ASM (I will add the full copy-pastable code at the bottom of this post, so I'm just zooming in on the relevant parts, here):
class AddingMethodVisitor extends MethodVisitor implements Opcodes{
int v;
public AddingMethodVisitor(int v, int api, MethodVisitor mv) {
super(api, mv);
this.v = v;
}
#Override
public void visitCode() {
super.visitCode();
mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number
/*if arg1 == the new version*/
mv.visitLdcInsn(v);
Label lSkip = new Label();
mv.visitInsn(LCMP);
mv.visitJumpInsn(IFNE, lSkip);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
/*else*/
mv.visitLabel(lSkip);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
}
and reload the class with ByteBuddy (again, full code at the bottom of the post):
ClassReader cr;
try {
/*note to self: don't forget the ``.getClassLoader()``*/
cr = new ClassReader(manipsClass.getClassLoader().getResourceAsStream( manipsClass.getName().replace('.','/') + ".class"));
}catch(IOException e){
throw new RuntimeException(e);
}
ClassWriter cw = new ClassWriter(cr, 0);
VersionAdder adder = new VersionAdder(C.latest + 1,Opcodes.ASM5,cw);
cr.accept(adder,0);
System.out.println("reloading C$Manipulatables class");
byte[] bytes = cw.toByteArray();
ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(manipsClass.getName(), bytes);
new ByteBuddy()
.redefine(manipsClass,classFileLocator)
.name(manipsClass.getName())
.make()
.load(C.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
;
C.latest++;
System.out.println("RELOADED");
}
}
This fails.
got 0
reloading C$Manipulatables class
Exception in thread "main" java.lang.VerifyError
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$1.apply(ClassReloadingStrategy.java:261)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:171)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4456)
at redefineconcept.CInserter.addNext(CInserter.java:60)
at redefineconcept.CInserter.run(CInserter.java:22)
at redefineconcept.CInserter.main(CInserter.java:16)
Process finished with exit code 1
In fact, this even fails when I comment the return null stmt generation (as I have in the full code provided below).
Apparently, java just doesn't like the way I've constructed my IF, even though it's essentially the code I got when I used asmifier on
public class B {
public Object run(long version, String field){
if(version == 2) {
return null;
}
return null;
}
}
which yielded
{
mv = cw.visitMethod(ACC_PUBLIC, "run", "(JLjava/lang/String;)Ljava/lang/Object;", null, null);
mv.visitCode();
mv.visitVarInsn(LLOAD, 1);
mv.visitLdcInsn(new Long(2L));
mv.visitInsn(LCMP);
Label l0 = new Label();
mv.visitJumpInsn(IFNE, l0);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(ACONST_NULL);
mv.visitInsn(ARETURN);
mv.visitMaxs(4, 4);
mv.visitEnd();
}
I simply left away everything after
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
because after that, the already existing part of the method follows.
I really do believe that there's something about that visitFrame that java doesn't like.
Let's say I change my visitCode s.t. it reads
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Work, you ..!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number
//
// /*if arg1 == the new version*/
// mv.visitLdcInsn(v);
// Label lSkip = new Label();
//
// mv.visitInsn(LCMP);
// mv.visitJumpInsn(IFNE, lSkip);
//
//// mv.visitInsn(ACONST_NULL);
//// mv.visitInsn(ARETURN);
//
// /*else*/
// mv.visitLabel(lSkip);
// mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
Then the redefinition works. I get the expected exception because the code falls through to the original if-else block that cannot handle the new version number, but I do get the output, at least.
got 0
reloading C$Manipulatables class
RELOADED
Work, you ..!
Exception in thread "main" java.lang.RuntimeException: unacceptable: v not found
at redefineconcept.C$Manipulatables.get(C.java:27)
at redefineconcept.C.get(C.java:10)
at redefineconcept.CInserter.run(CInserter.java:23)
at redefineconcept.CInserter.main(CInserter.java:16)
I'd very much appreciate some help in resolving this.
What's the correct way to insert a new if stmt that java will accept?
FULL CODE
C.java
(Note that the class C$Manipulatables is necessary, because ByteBuddy cannot redefine classes that have static initialisers.)
package redefineconcept;
public class C {
public static volatile int latest = 0;
public static final C$Manipulatables manips = new C$Manipulatables();
public int get(){
int v = latest;
return manips.get(v,"").value;
}
}
class Wrapper{
int value;
public Wrapper(int value){
this.value = value;
}
}
class C$Manipulatables{
public Wrapper get(long v, String f){
if(v == 0){
return new Wrapper(0);
}else{
throw new RuntimeException("unacceptable: v not found");
}
}
}
CInserter.java
package redefineconcept;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.jar.asm.*;
import java.io.IOException;
public class CInserter {
public static void main(String[] args) {
ByteBuddyAgent.install();
new CInserter().run();
}
private void run(){
C c = new C();
System.out.println("got " + c.get());
addNext();
System.out.println("got " + c.get()); //should trigger nullptr exception
}
private void addNext(){
Object manips;
String manipsFld = "manips";
try {
manips = C.class.getDeclaredField(manipsFld).get(null);
}catch(NoSuchFieldException | IllegalAccessException e){
throw new RuntimeException(e);
}
Class<?> manipsClass = manips.getClass();
assert(manipsClass.getName().equals("redefineconcept.C$Manipulatables"));
ClassReader cr;
try {
/*note to self: don't forget the ``.getClassLoader()``*/
cr = new ClassReader(manipsClass.getClassLoader().getResourceAsStream( manipsClass.getName().replace('.','/') + ".class"));
}catch(IOException e){
throw new RuntimeException(e);
}
ClassWriter cw = new ClassWriter(cr, 0);
VersionAdder adder = new VersionAdder(C.latest + 1,Opcodes.ASM5,cw);
cr.accept(adder,0);
System.out.println("reloading C$Manipulatables class");
byte[] bytes = cw.toByteArray();
ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(manipsClass.getName(), bytes);
new ByteBuddy()
.redefine(manipsClass,classFileLocator)
.name(manipsClass.getName())
.make()
.load(C.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
;
C.latest++;
System.out.println("RELOADED");
}
}
class VersionAdder extends ClassVisitor{
private int v;
public VersionAdder(int v, int api, ClassVisitor cv) {
super(api, cv);
this.v = v;
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if(mv != null && name.equals("get")){
return new AddingMethodVisitor(v,Opcodes.ASM5,mv);
}
return mv;
}
class AddingMethodVisitor extends MethodVisitor implements Opcodes{
int v;
public AddingMethodVisitor(int v, int api, MethodVisitor mv) {
super(api, mv);
this.v = v;
}
#Override
public void visitCode() {
super.visitCode();
mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number
/*if arg1 == the new version*/
mv.visitLdcInsn(v);
Label lSkip = new Label();
mv.visitInsn(LCMP);
mv.visitJumpInsn(IFNE, lSkip);
// mv.visitInsn(ACONST_NULL);
// mv.visitInsn(ARETURN);
/*else*/
mv.visitLabel(lSkip);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
}
}
One bug I noticed in your code is the following line
mv.visitLdcInsn(v);
The intent of the code is to create and load a long constant, but v has type int, so an integer constant will be created instead, thus creating a type error in the bytecode when you compare it with lcmp on the next line. visitLdcInsn will create a different constant type depending on the type of Object you pass in, so the argument needs to be the exact type you want.
On a side note, you don't need LDC in the first place to create a long constant of value 1, because there is a dedicated bytecode instruction for this, lconst_1. In ASM, this should be something like visitInsn(LCONST_1);

Find methods with modified code between two versions of a Java class

I'd like to find the methods which changed between two arbitrary Java files.
What I've Tried
I've tried using diff (GNU diffutils 3.3) to find changes to lines in the file and diff --show-c-function connect those lines to the changed method. Unfortunately, in Java this lists the class, not the function.
I also tried git diff which seems to properly be able to find the changed function (at least as displayed on GitHub), but it doesn't always list the full signature and my files are not in the same Git repository (https://github.com/apache/commons-math/commit/34adc606601cb578486d4a019b4655c5aff607b5).
Desired Results
Input:
~/repos/d4jBugs/Math_54_buggy/src/main/java/org/apache/commons/math/dfp/Dfp.java
~/repos/d4jBugs/Math_54_fixed/src/main/java/org/apache/commons/math/dfp/Dfp.java
State of Files:
The changed methods between those two files are public double toDouble() and protected Dfp(final DfpField field, double x)
Output: (fully qualified names)
org.apache.commons.math.dfp.Dfp.toDouble()
org.apache.commons.math.dfp.Dfp(final DfpField field, double x)
Summary
Can I find the modified methods with the GNU diffutils tool or git diff and if yes, how would I do that? (Note: I'm not bound to those tools and am happy to install something else if needed.)
I used JavaParser 3.4.4, but it probably works but has not been tested with other versions.
It can be imported in Gradle with:
compile group: 'com.github.javaparser', name: 'javaparser-core', version: '3.4.4'
You can use my class like:
HashSet<String> changedMethods = MethodDiff.methodDiffInClass(
oldFileNameWithPath,
newFileNameWithPath
);
MethodDiff Source:
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.CallableDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.ConstructorDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.comments.Comment;
import com.github.javaparser.printer.PrettyPrinterConfiguration;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Created by Loren Klingman on 10/19/17.
* Finds Changes Between Methods of Two Java Source Files
*/
public class MethodDiff {
private static PrettyPrinterConfiguration ppc = null;
class ClassPair {
final ClassOrInterfaceDeclaration clazz;
final String name;
ClassPair(ClassOrInterfaceDeclaration c, String n) {
clazz = c;
name = n;
}
}
public static PrettyPrinterConfiguration getPPC() {
if (ppc != null) {
return ppc;
}
PrettyPrinterConfiguration localPpc = new PrettyPrinterConfiguration();
localPpc.setColumnAlignFirstMethodChain(false);
localPpc.setColumnAlignParameters(false);
localPpc.setEndOfLineCharacter("");
localPpc.setIndent("");
localPpc.setPrintComments(false);
localPpc.setPrintJavadoc(false);
ppc = localPpc;
return ppc;
}
public static <N extends Node> List<N> getChildNodesNotInClass(Node n, Class<N> clazz) {
List<N> nodes = new ArrayList<>();
for (Node child : n.getChildNodes()) {
if (child instanceof ClassOrInterfaceDeclaration) {
// Don't go into a nested class
continue;
}
if (clazz.isInstance(child)) {
nodes.add(clazz.cast(child));
}
nodes.addAll(getChildNodesNotInClass(child, clazz));
}
return nodes;
}
private List<ClassPair> getClasses(Node n, String parents, boolean inMethod) {
List<ClassPair> pairList = new ArrayList<>();
for (Node child : n.getChildNodes()) {
if (child instanceof ClassOrInterfaceDeclaration) {
ClassOrInterfaceDeclaration c = (ClassOrInterfaceDeclaration)child;
String cName = parents+c.getNameAsString();
if (inMethod) {
System.out.println(
"WARNING: Class "+cName+" is located inside a method. We cannot predict its name at"
+ " compile time so it will not be diffed."
);
} else {
pairList.add(new ClassPair(c, cName));
pairList.addAll(getClasses(c, cName + "$", inMethod));
}
} else if (child instanceof MethodDeclaration || child instanceof ConstructorDeclaration) {
pairList.addAll(getClasses(child, parents, true));
} else {
pairList.addAll(getClasses(child, parents, inMethod));
}
}
return pairList;
}
private List<ClassPair> getClasses(String file) {
try {
CompilationUnit cu = JavaParser.parse(new File(file));
return getClasses(cu, "", false);
} catch (FileNotFoundException f) {
throw new RuntimeException("EXCEPTION: Could not find file: "+file);
}
}
public static String getSignature(String className, CallableDeclaration m) {
return className+"."+m.getSignature().asString();
}
public static HashSet<String> methodDiffInClass(String file1, String file2) {
HashSet<String> changedMethods = new HashSet<>();
HashMap<String, String> methods = new HashMap<>();
MethodDiff md = new MethodDiff();
// Load all the method and constructor values into a Hashmap from File1
List<ClassPair> cList = md.getClasses(file1);
for (ClassPair c : cList) {
List<ConstructorDeclaration> conList = getChildNodesNotInClass(c.clazz, ConstructorDeclaration.class);
List<MethodDeclaration> mList = getChildNodesNotInClass(c.clazz, MethodDeclaration.class);
for (MethodDeclaration m : mList) {
String methodSignature = getSignature(c.name, m);
if (m.getBody().isPresent()) {
methods.put(methodSignature, m.getBody().get().toString(getPPC()));
} else {
System.out.println("Warning: No Body for "+file1+" "+methodSignature);
}
}
for (ConstructorDeclaration con : conList) {
String methodSignature = getSignature(c.name, con);
methods.put(methodSignature, con.getBody().toString(getPPC()));
}
}
// Compare everything in file2 to what is in file1 and log any differences
cList = md.getClasses(file2);
for (ClassPair c : cList) {
List<ConstructorDeclaration> conList = getChildNodesNotInClass(c.clazz, ConstructorDeclaration.class);
List<MethodDeclaration> mList = getChildNodesNotInClass(c.clazz, MethodDeclaration.class);
for (MethodDeclaration m : mList) {
String methodSignature = getSignature(c.name, m);
if (m.getBody().isPresent()) {
String body1 = methods.remove(methodSignature);
String body2 = m.getBody().get().toString(getPPC());
if (body1 == null || !body1.equals(body2)) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(methodSignature.replace(" ", ""));
}
} else {
System.out.println("Warning: No Body for "+file2+" "+methodSignature);
}
}
for (ConstructorDeclaration con : conList) {
String methodSignature = getSignature(c.name, con);
String body1 = methods.remove(methodSignature);
String body2 = con.getBody().toString(getPPC());
if (body1 == null || !body1.equals(body2)) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(methodSignature.replace(" ", ""));
}
}
// Anything left in methods was only in the first set and so is "changed"
for (String method : methods.keySet()) {
// Javassist doesn't add spaces for methods with 2+ parameters...
changedMethods.add(method.replace(" ", ""));
}
}
return changedMethods;
}
private static void removeComments(Node node) {
for (Comment child : node.getAllContainedComments()) {
child.remove();
}
}
}

How to prevent proguard from modifying the fields in my method

How do I prevent proguard from modifying the field names in my method?
I know we can use -keep, -keepclassmembers..etc but these seems to only preserve the member names and NOT the variables that are used inside.
Here's what happened:
Before:
private static URL getPluginImageURL(Object plugin, String name) throws Exception {
// try to work with 'plugin' as with OSGI BundleContext
try {
Class<?> BundleClass = Class.forName("org.osgi.framework.Bundle"); //$NON-NLS-1$
Class<?> BundleContextClass = Class.forName("org.osgi.framework.BundleContext"); //$NON-NLS-1$
if (BundleContextClass.isAssignableFrom(plugin.getClass())) {
Method getBundleMethod = BundleContextClass.getMethod("getBundle", new Class[0]); //$NON-NLS-1$
Object bundle = getBundleMethod.invoke(plugin, new Object[0]);
//
Class<?> PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$
Constructor<?> pathConstructor = PathClass.getConstructor(new Class[]{String.class});
Object path = pathConstructor.newInstance(new Object[]{name});
//
Class<?> IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$
Class<?> PlatformClass = Class.forName("org.eclipse.core.runtime.Platform"); //$NON-NLS-1$
Method findMethod = PlatformClass.getMethod("find", new Class[]{BundleClass, IPathClass}); //$NON-NLS-1$
return (URL) findMethod.invoke(null, new Object[]{bundle, path});
}
} catch (Throwable e) {
// Ignore any exceptions
}
// else work with 'plugin' as with usual Eclipse plugin
{
Class<?> PluginClass = Class.forName("org.eclipse.core.runtime.Plugin"); //$NON-NLS-1$
if (PluginClass.isAssignableFrom(plugin.getClass())) {
//
Class<?> PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$
Constructor<?> pathConstructor = PathClass.getConstructor(new Class[]{String.class});
Object path = pathConstructor.newInstance(new Object[]{name});
//
Class<?> IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$
Method findMethod = PluginClass.getMethod("find", new Class[]{IPathClass}); //$NON-NLS-1$
return (URL) findMethod.invoke(plugin, new Object[]{path});
}
}
return null;
}
After proguard:
private static URL getPluginImageURL(Object plugin, String name)
throws Exception
{
try
{
localClass1 = Class.forName("org.osgi.framework.Bundle");
localClass2 = Class.forName("org.osgi.framework.BundleContext");
if (localClass2.isAssignableFrom(plugin.getClass())) {
localObject1 = localClass2.getMethod("getBundle", new Class[0]);
localObject2 = ((Method)localObject1).invoke(plugin, new Object[0]);
localClass3 = Class.forName("org.eclipse.core.runtime.Path");
localObject3 = localClass3.getConstructor(new Class[] { String.class });
Object localObject4 = ((Constructor)localObject3).newInstance(new Object[] { name });
Class localClass4 = Class.forName("org.eclipse.core.runtime.IPath");
Class localClass5 = Class.forName("org.eclipse.core.runtime.Platform");
Method localMethod = localClass5.getMethod("find", new Class[] { localClass1, localClass4 });
return (URL)localMethod.invoke(null, new Object[] { localObject2, localObject4 });
}
}
catch (Throwable localThrowable)
{
Class localClass2;
Object localObject1;
Object localObject2;
Class localClass3;
Object localObject3;
Class localClass1 = Class.forName("org.eclipse.core.runtime.Plugin");
if (localClass1.isAssignableFrom(plugin.getClass()))
{
localClass2 = Class.forName("org.eclipse.core.runtime.Path");
localObject1 = localClass2.getConstructor(new Class[] { String.class });
localObject2 = ((Constructor)localObject1).newInstance(new Object[] { name });
localClass3 = Class.forName("org.eclipse.core.runtime.IPath");
localObject3 = localClass1.getMethod("find", new Class[] { localClass3 });
return (URL)((Method)localObject3).invoke(plugin, new Object[] { localObject2 });
}
}
return null;
}
Notice how localObject1 and localObject2 no longer have Class declaration? Doesn't this make the code syntactically incorrect?
Help...
It's your decompiler that produces the syntactically incorrect code. You could try a different decompiler.
ProGuard indeed removes the local variable names, since they are just optional debugging information. They are not required by the virtual machine.

Categories