How to prevent proguard from modifying the fields in my method - java

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.

Related

On parsing gson.toJson(obj) giving null

wWhen I am passing object of local-inner-class ShipAddress to toJson() method of Gson class this is returning null on parsing it.
public class CrusialDateRest {
public String getShippingAddressesDetails() {
Gson gson = new GsonBuilder().create();
try {
Collection<ImplAddress> savedAddressBeans = new ArrayList<ImplAddress>();
Collection<CtFlexField> countryFields = new ArrayList<CtFlexField>();
Collection<CtFlexField> debitorFields = new ArrayList<CtFlexField>();
class ShipAddress{
Collection<ImplAddress> savedAddressBean = new ArrayList<ImplAddress>();
Collection<CtFlexField> countryField = new ArrayList<CtFlexField>();
Collection<CtFlexField> debitorField = new ArrayList<CtFlexField>();
ShipAddress( Collection<ImplAddress> savedAddressBeans, Collection<CtFlexField> countryFields,Collection<CtFlexField> debitorFields){
savedAddressBean=savedAddressBeans;
countryField=countryFields;
debitorField=debitorFields;
}
}
String addrId= XmlParser.getNodeValue(address, Statics.BUYFLOW_NAMESPACE, "AddressId");
String addrStreet1 = XmlParser.getNodeValue(address, Statics.BUYFLOW_NAMESPACE, "AddrStreet1");
String addrStreet2 = XmlParser.getNodeValue(address, Statics.BUYFLOW_NAMESPACE, "AddrStreet2");
String addrStreet3 = XmlParser.getNodeValue(address, Statics.BUYFLOW_NAMESPACE, "AddrStreet3");
ImplAddress impladdress = new ImplAddress();
impladdress.setAddressId(addrId);
impladdress.setAddrStreet1(addrStreet1);
impladdress.setAddrStreet2(addrStreet2);
impladdress.setAddrStreet3(addrStreet3);
savedAddressBeans.add(impladdress);
}
CtFlexField[] flexField = flexFields.getFlexField();
for (CtFlexField flex : flexField) {
if(flex.getBundle().equalsIgnoreCase("Countries")){
countryFields.add(flex);
}
else if(flex.getBundle().equalsIgnoreCase("CommonBundle")){
debitorFields.add(flex);
}
}
jsonResponse = gson.toJson(new ShipAddress(savedAddressBeans,countryFields,debitorFields));
OUT.debug("jsonResponse--"+jsonResponse);
} catch (Exception e) {
OUT.error("rest method getShippingAddresses error", e);
}
return jsonResponse;
}
}
Should I make inner class outside method?
Or is this a serialization issue?
It's most likely a visibility issue.
Either move ShipAddress into its own class file, or make it a public static inner class.
Note that public static classes cannot be declared inside of methods, you would have to move the class out of getShippingAddressDetails()method.

method.invoke generates IllegalArgumentException:wrong number of arguments

I have created EasyMock test cases for one method which has method.invoke().One test case runs fine with this code.The second one which should cover the first "if" condition creates this IlegalArguent Exception:Wrong number of arguments.I don't understand which one is incorrect.whether original code or test case.
Please help.
Original code:
#RequestMapping(value = "/invoke/{service}/{method}", method = RequestMethod.POST)
public #ResponseBody
Object invokeService(#PathVariable("service") String className,
#PathVariable("method") String method,
#RequestParam("ms") String signature,
#RequestParam("r") String responseType, #RequestBody String[] body)
throws Exception {
if (applicationContext != null
&& applicationContext.containsBean(className)) {
Object obj = applicationContext.getBean(className);
String temp[] = signature.split(",");
Object[] arguments = new Object[temp.length];
Class[] parameterTypes = new Class[temp.length];
for (int i = 0; i < temp.length; i++) {
if(temp[i] != null && !temp[i].isEmpty()) {
Class cls = Class.forName(temp[i]);
parameterTypes[i] = cls;
if (temp[i].startsWith("java.lang.")) {
arguments[i] = body[i];
} else {
try {
arguments[i] = mapper.readValue(body[i], cls);
} catch (Exception e) {
// e.printStackTrace();
arguments[i] = body[i];
}
}
}
}
Method m = null;
if(null !=signature && !signature.isEmpty()) {
m = obj.getClass().getMethod(method, parameterTypes);
} else {
m = obj.getClass().getMethod(method);
}
Object response = m.invoke(obj);
return response;
} else {
throw new Exception("ApplicationContext not properly set");
}
}
Test1(success):
#Test
public void testInvokeServiceNotJavaLang() throws Exception{
Object obj = new Object();
String[] body ={ "body" };
EasyMock.expect(applicationContext.containsBean("String")).andReturn(true);
EasyMock.expect(applicationContext.getBean("String")).andReturn(obj);
EasyMock.replay(applicationContext);
moduleInvocation.invokeService("String", "toString","", "responseType",body );
EasyMock.verify(applicationContext);
}
Test2(IllegalArgumentException:Wrong number of arguments)
#Test
public void testInvokeService() throws Exception{
Object obj = new Object();
String[] body ={ "body" };
EasyMock.expect(applicationContext.containsBean("Object")).andReturn(true);
EasyMock.expect(applicationContext.getBean("Object")).andReturn(obj);
EasyMock.replay(applicationContext);
moduleInvocation.invokeService("Object", "equals", "java.lang.Object", "responseType",body );
EasyMock.verify(applicationContext);
}
Problem is with your code, In first case you are calling a method String#toString() which does not have any parameters and it gets called successfully. In second case you are calling Object#equals() method which expects one argument as well so you need to pass that argument value as well while using m.invoke(), so your code should be for second case
m.invoke(obj,new Object[]{<object to be compared>});
and for fist case this will suffice
m.invoke(obj,null);
Hope this helps.
EDIT
You should write it something like this:
Method m = null;
if(null !=signature && !signature.isEmpty()) {
m = obj.getClass().getMethod(method, parameterTypes);
} else {
m = obj.getClass().getMethod(method);
arguments = null;
}
Object response = m.invoke(obj, arguments);
return response;

BeanSerializer/BeanDeserializer Axis Generated Object

I have a large number of axis generated objects from several WSDLs, I need a generic solution to store the objects in xml format in the database, but also load them back in java when needed.
This is what I've made so far:
private String serializeAxisObject(Object obj) throws Exception {
if (obj == null) {
return null;
}
StringWriter outStr = new StringWriter();
TypeDesc typeDesc = getAxisTypeDesc(obj);
QName qname = typeDesc.getXmlType();
String lname = qname.getLocalPart();
if (lname.startsWith(">") && lname.length() > 1)
lname = lname.substring(1);
qname = new QName(qname.getNamespaceURI(), lname);
AxisServer server = new AxisServer();
BeanSerializer ser = new BeanSerializer(obj.getClass(), qname, typeDesc);
SerializationContext ctx = new SerializationContext(outStr,
new MessageContext(server));
ctx.setSendDecl(false);
ctx.setDoMultiRefs(false);
ctx.setPretty(true);
try {
ser.serialize(qname, new AttributesImpl(), obj, ctx);
} catch (final Exception e) {
throw new Exception("Unable to serialize object "
+ obj.getClass().getName(), e);
}
String xml = outStr.toString();
return xml;
}
private Object deserializeAxisObject(Class<?> cls, String xml)
throws Exception {
final String SOAP_START = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header /><soapenv:Body>";
final String SOAP_START_XSI = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Header /><soapenv:Body>";
final String SOAP_END = "</soapenv:Body></soapenv:Envelope>";
Object result = null;
try {
Message message = new Message(SOAP_START + xml + SOAP_END);
result = message.getSOAPEnvelope().getFirstBody()
.getObjectValue(cls);
} catch (Exception e) {
try {
Message message = new Message(SOAP_START_XSI + xml + SOAP_END);
result = message.getSOAPEnvelope().getFirstBody()
.getObjectValue(cls);
} catch (Exception e1) {
throw new Exception(e1);
}
}
return result;
}
private TypeDesc getAxisTypeDesc(Object obj) throws Exception {
final Class<? extends Object> objClass = obj.getClass();
try {
final Method methodGetTypeDesc = objClass.getMethod("getTypeDesc",
new Class[] {});
final TypeDesc typeDesc = (TypeDesc) methodGetTypeDesc.invoke(obj,
new Object[] {});
return (typeDesc);
} catch (final Exception e) {
throw new Exception("Unable to get Axis TypeDesc for "
+ objClass.getName(), e);
}
}
I have fixed it, I will leave this here if anyone else needs to use it
Have fun.
I have fixed it, I will leave this here if anyone else needs to use it. See the updated version.
Thank you

marshalling a log file into an xml file - Java

I have a log file with the following output:
2226:org.powertac.common.TariffSpecification::6::new::1::CONSUMPTION
2231:org.powertac.common.Rate::7::new
2231:org.powertac.common.Rate::7::withValue::-0.5
2232:org.powertac.common.Rate::7::setTariffId::6
2232:org.powertac.common.TariffSpecification::6::addRate::7
2233:org.powertac.common.Tariff::6::new::6
2234:org.powertac.common.TariffSpecification::8::new::1::INTERRUPTIBLE_CONSUMPTION
2234:org.powertac.common.Rate::9::new
2234:org.powertac.common.Rate::9::withValue::-0.5
2235:org.powertac.common.Rate::9::setTariffId::8
After I parse the file, have the following pattern:
<id>:<full_classname>::<order_of_execution>::<new_or_method>::<params>
The parser works nicely, and does what I expect. Now, my goal is to marshalling that same instruction into a XML file. I'm totally unfamiliar with this kind of task.
So, the XML would have to contain both new objects and methods call.
I know using the Reflection API I would use the <full_classname> to create an object of that class:
Class<?> cl = Class.forName( className );
How could I generate such XML file from that Class object? Do I have to have a data-structure or a way to take all the methods and fields of the object and write them to the xml file? I know the Reflection API has such methods, but I would need a more general / sample idea of how could I accomplish my task.
I started to write down this method, but I'm not sure how would it work:
// would send in the object to be marshalled.
public void toXML(Object obj){
try {
JAXBContext context = JAXBContext.newInstance(Object.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Here is a sample of the parsed file:
171269 org.powertac.common.Order 171417 new 4
171270 org.powertac.common.Order 171418 new 4
171271 org.powertac.common.Order 171419 new 4
The parse method looks like:
public void parse() throws ClassNotFoundException{
try{
//
// assure file exists before parsing
//
FileReader fr = new FileReader( this.filename );
BufferedReader textReader = new BufferedReader( fr );
String line;
File input = new File("test.xml");
//Integer id = 1;
while(( line = textReader.readLine()) != null ){
Pattern p = Pattern.compile("([^:]+):([^:]+)::([\\d]+)::([^:]+)::(.+)");
Matcher m = p.matcher( line );
if (m.find()) {
int id = Integer.parseInt(m.group(1));
String className = m.group(2);
int orderOfExecution = Integer.valueOf( m.group( 3 ));
String methodNameOrNew = m.group(4);
String[] arguments = m.group(5).split("::");
//
// there is the need to create a new object
//
if( methodNameOrNew.compareTo( "new" ) == 0 ){
//
// inner class
//
if( className.contains("$") == true){
continue;
}
else if( className.contains("genco")){
continue;
}
System.out.println("Loading class: " + className);
LogEntry le = new LogEntry(id, className, orderOfExecution, methodNameOrNew, arguments.toString());
Serializer ser = new Persister();
ser.write(le, input);
id++;
System.out.printf("%s %s %d %s %d\n", id, className, orderOfExecution, methodNameOrNew, arguments.length);
}
}
}
textReader.close();
}
catch( IOException ex ){
ex.printStackTrace();
}
catch( ArrayIndexOutOfBoundsException ex){
ex.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void write() throws Exception{
File file = new File("test.xml");
Serializer ser = new Persister();
for(LogEntry entry : entries){
ser.write(entry, file);
}
}
Here is a first try using Simple XML library:
#Default()
public class LogEntry
{
private int id;
private Object classInstance;
private String orderOfExecution;
private String newOrMethod;
private String params;
// throws 'Exception' only for testing
public LogEntry(int id, String className, String orderOfExecution, String newOrMethod, String params) throws Exception
{
this.id = id;
this.classInstance = Class.forName(className).newInstance();
this.orderOfExecution = orderOfExecution;
this.newOrMethod = newOrMethod;
this.params = params;
}
// getter / setter
}
And how do make XML out of the class LogEntry:
// Here is an example of an entry
LogEntry le = new LogEntry(3, "com.example.MyClass", "abc", "def", "ghi");
Serializer ser = new Persister();
ser.write(le, new File("test.xml"));
Simple XML is very easy to use, see here for tutorials and examples.
You can custumize the whole XML with the Annotations in the LogEntry Class, however you can also let #Default() do everything for you :-)
LogEntry:
#Default()
public class LogEntry
{
private int id;
private Object classInstance;
private int orderOfExecution;
private String newOrMethod;
private List<Object> args;
public LogEntry(int id, Object classInstance, int orderOfExecution, String newOrMethod, List<Object> args)
{
this.id = id;
this.classInstance = classInstance;
this.orderOfExecution = orderOfExecution;
this.newOrMethod = newOrMethod;
this.args = args;
}
public LogEntry() { }
// getter / setter / toString / ... here
}
parse Method:
// Here all entries are saved
private List<LogEntry> entries = new ArrayList<>();
// ...
public void parse() throws Exception
{
// Don't compile this in a loop!
Pattern p = Pattern.compile("([^:]+):([^:]+)::([\\d]+)::([^:]+)::(.+)");
FileReader fr = new FileReader(this.filename);
BufferedReader textReader = new BufferedReader(fr);
String line;
while( (line = textReader.readLine()) != null )
{
Matcher m = p.matcher(line);
if( m.find() )
{
LogEntry entry = new LogEntry();
entry.setId(Integer.valueOf(m.group(1)));
String className = m.group(2);
entry.setOrderOfExecution(Integer.valueOf(m.group(3)));
String methodNameOrNew = m.group(4);
entry.setNewOrMethod(methodNameOrNew); // required in LogEntry?
Object[] arguments = m.group(5).split("::");
entry.setArgs(Arrays.asList(arguments));
if( methodNameOrNew.equals("new") )
{
if( className.contains("$") == true || className.contains("genco") )
continue;
createInstance(className, arguments);
}
else
{
callMethod(className, methodNameOrNew, arguments);
}
// XXX: for testing - set the instance 'not null'
entry.setClassInstance("only for testing");
entries.add(entry);
}
}
textReader.close();
}
Edit:
Lets say your parse()-Method, the List etc are in the Class Example:
#Root
public class Example
{
private File filename = new File("test.txt");
#ElementList
private List<LogEntry> entries = new ArrayList<>();
// ...
// Only 'entries' is annotated as entry -> only it will get serialized
public void storeToXml(File f) throws Exception
{
Serializer ser = new Persister();
ser.write(this, f);
}
public void parse() throws Exception
{
// ...
}
}
Note: For this example i've added entry.setClassInstance("only for testing"); above entries.add(...), else the instance is null.
Edit #2: Helper methods for parse()
private Object createInstance(String className, Object args[])
{
// TODO
return null;
}
private void callMethod(String className, String methodName, Object args[])
{
// TODO
}

Java ASM Bytecode Modification-Changing method bodies

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.

Categories