I can't load local variables for invokedynamic in ASM JAVA - java

I have create a mini-logger for methods, and I use ASM. I need to determine by descriptor method parameters and print it.
But I have an error
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
ru/otus/TestLogging.calc(IFD)V #6: invokedynamic
Reason:
Type 'java/io/PrintStream' (current frame, stack[4]) is not assignable to double_2nd
Current Frame:
bci: #6
flags: { }
locals: { 'ru/otus/TestLogging', integer, float, double, double_2nd }
stack: { 'ru/otus/TestLogging', float, double, double_2nd, 'java/io/PrintStream' }
Bytecode:
0000000: 2a24 29b2 0007 ba00 3e00 00b6 0011 b200
0000010: 071b 2429 ba00 0d00 00b6 0011 b1
This is code my Agent
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
#Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.contains("ru/otus/")) {
return changeMethod(classfileBuffer, className);
}
return classfileBuffer;
}
});
}
private static byte[] changeMethod(byte[] originalClass, String className) {
ClassReader reader = new ClassReader(originalClass);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ArrayList<String> list = new ArrayList<>();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {
#Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
System.out.println("visitMethod: access="+access+" name="+name+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
Method thisMethod = new Method(name, descriptor);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, super.visitMethod(access, name, descriptor, signature, exceptions), thisMethod, className);
return mv;
}
};
reader.accept(visitor, Opcodes.ASM5);
for(String methodName : list) {
System.out.println(methodName);
}
byte[] finalClass = writer.toByteArray();
if(className.contains("Test")) {
try (OutputStream fos = new FileOutputStream("TestLogging.class")) {
fos.write(finalClass);
} catch (Exception e) {
e.printStackTrace();
}
}
return writer.toByteArray();
}
static class MethodAnnotationScanner extends MethodVisitor {
private Method thisMethod;
private boolean isChangeMethod = false;
private String className = null;
private StringBuilder descriptor = new StringBuilder("(");
public MethodAnnotationScanner(int api, MethodVisitor methodVisitor, Method thisMethod, String className) {
super(api, methodVisitor);
this.thisMethod = thisMethod;
this.className = className;
}
#Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
#Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitVarInsn(Opcodes.ALOAD, 0);
int i = 1;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, i);
++i;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, i);
++i;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, i);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, i);
}
i++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
super.visitMaxs(0, 0);
}
if (mv != null) {
super.visitCode();
}
super.visitEnd();
}
}
}
I have two classes to reproduce this feature.
first - TestLogging
second - AutoLogger
In the first class I have a methods which need to logged,
In second it's start class contains method main.
This is my project

You are pushing the arguments for the string concatenation before reading the field System.out, followed by attempting to perform the string concatenation. So the invokedynamic instruction intended to perform the string concatenation finds a mismatching PrintStream on the operand stack.
A simple fix is to change the instructions
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
to
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(i));
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
super.visitInsn(Opcodes.SWAP);
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Or move the GETSTATIC before the code that will push the concat arguments, to the place where you perform the obsolete super.visitVarInsn(Opcodes.ALOAD, 0);. Then, you don’t need SWAP.
But there are more problems with the code. You are counting the local variables while pushing the values, correctly considering long and double to take two variables, but then, you are using the same number in the ", param: \u0001".repeat(i) expression, which will tell the StringConcatFactory that there were two values in case of long and double. You need to separate the counters. Also, you are not pushing reference type arguments, but since you are counting them and including them in the signature for the concat invocation, you must also push them to the operand stack.
Further, while not having an effect here, the visitMaxs(0, 0) call and the visitEnd() call are inappropriate. You are injecting at the beginning of the code and the other visit calls, which you will not intercept, will follow, including an automatically performed visitMaxs and visitEnd.
With all fixes, the code looks like
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0;
for(Type arg : thisMethod.getArgumentTypes()) {
this.descriptor.append(arg.getDescriptor());
if (arg.getDescriptor().equals("J")) {
super.visitVarInsn(Opcodes.LLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("D")) {
super.visitVarInsn(Opcodes.DLOAD, varIndex);
++varIndex;
} else if (arg.getDescriptor().equals("F")) {
super.visitVarInsn(Opcodes.FLOAD, varIndex);
} else if(arg.getDescriptor().equals("I")) {
super.visitVarInsn(Opcodes.ILOAD, varIndex);
} else {
super.visitVarInsn(Opcodes.ALOAD, varIndex);
}
varIndex++;
numArgs++;
}
Handle handle = new Handle(
H_INVOKESTATIC,
Type.getInternalName(java.lang.invoke.StringConcatFactory.class),
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Object[].class).toMethodDescriptorString(),
false);
this.descriptor.append(")Ljava/lang/String;");
super.visitInvokeDynamicInsn("makeConcatWithConstants", this.descriptor.toString(), handle, "executed method: " + this.thisMethod.getName() + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
As a side note, when you are pushing all arguments for the concatenation, you can simplify the descriptor construction, as the concat descriptor is almost identical to the method’s descriptor; you only have to replace the return type with Ljava/lang/String;.
You can do the entire operation without dealing with Method and Type objects, just using the two strings already passed to visitMethod.
#Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
System.out.println("visitMethod: access="+access+" name="+name
+" desc="+descriptor+" signature="+signature+" exceptions="+exceptions);
MethodVisitor mv = new MethodAnnotationScanner(Opcodes.ASM5, name, descriptor,
super.visitMethod(access, name, descriptor, signature, exceptions));
return mv;
}
static class MethodAnnotationScanner extends MethodVisitor {
private boolean isChangeMethod;
private final String name, descriptor;
public MethodAnnotationScanner(int api, String name,
String methodDesciptor, MethodVisitor methodVisitor){
super(api, methodVisitor);
this.name = name;
this.descriptor = methodDesciptor;
}
#Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc="+desc+" visible="+visible);
if(desc.contains("ru/otus/annotations/Log")) {
this.isChangeMethod = true;
return super.visitAnnotation(desc, visible);
}
this.isChangeMethod = false;
return super.visitAnnotation(desc, visible);
}
#Override
public void visitCode() {
if(this.isChangeMethod) {
super.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
int varIndex = 1, numArgs = 0, p;
for(p = 1; descriptor.charAt(p) != ')'; p++) {
switch(descriptor.charAt(p)) {
case 'J':
super.visitVarInsn(Opcodes.LLOAD, varIndex); ++varIndex; break;
case 'D':
super.visitVarInsn(Opcodes.DLOAD, varIndex); ++varIndex; break;
case 'F': super.visitVarInsn(Opcodes.FLOAD, varIndex); break;
case 'I': super.visitVarInsn(Opcodes.ILOAD, varIndex); break;
case 'L': super.visitVarInsn(Opcodes.ALOAD, varIndex);
p = descriptor.indexOf(';', p);
break;
case '[': super.visitVarInsn(Opcodes.ALOAD, varIndex);
do {} while(descriptor.charAt(++p)=='[');
if(descriptor.charAt(p) == 'L') p = descriptor.indexOf(';', p);
break;
default: throw new IllegalStateException(descriptor);
}
varIndex++;
numArgs++;
}
String ret = "Ljava/lang/String;";
String concatSig = new StringBuilder(++p + ret.length())
.append(descriptor, 0, p).append(ret).toString();
Handle handle = new Handle(
H_INVOKESTATIC,
"java/lang/invoke/StringConcatFactory",
"makeConcatWithConstants",
MethodType.methodType(CallSite.class, MethodHandles.Lookup.class,
String.class, MethodType.class, String.class, Object[].class)
.toMethodDescriptorString(),
false);
super.visitInvokeDynamicInsn("makeConcatWithConstants", concatSig, handle,
"executed method: " + name + ", param: \u0001".repeat(numArgs));
super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitCode();
}
}

Related

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.

Getting the type of method parameters in reflection

I work with reflection. And I need to get the parameter method of my set () entity to call the corresponding fill method in accordance with the type.
try{
Class clazz = aClass.getClass();
Object object = clazz.newInstance();
while (clazz != Object.class){
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
if (method.isAnnotationPresent(ProductAnnotation.class)) {
Object[] strategyObj = new Object[1];
if (method.getReturnType().getName().equals("int")) { //reflexion never comes in if
strategyObj[0] = strategy.setInt(bundle.getString(method.getName().substring(3).toLowerCase()));
method.invoke(object, strategyObj);
}if (method.getParameterTypes().getClass().getTypeName().equals("String")){ //reflexion never comes in if
strategyObj[0] = strategy.setString(bundle.getString(method.getName().substring(3).toLowerCase()));
method.invoke(object, strategyObj);
}
}
}
clazz = clazz.getSuperclass();
}
return (FlyingMachine) object;
} catch (IllegalAccessException | IOException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
I tried to use getReturnedType () and getParametrTypes (), but the reflexion does not enter any condition. What was I wrong about?
My Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(value = ElementType.METHOD)
public #interface ProductAnnotation {
String value();
}
Methods that should cause reflection.Depending on the type of method, call one of these methods for further processing and filling in the data.
#Override
public int setInt(String title) throws IOException {
String line = null;
checkValue = true;
while (checkValue) {
System.out.println(title + "-->");
line = reader.readLine();
if (line.matches("\\d*")) {
System.out.println(title + " = " + Integer.parseInt(line));
checkValue = false;
} else {
System.out.println("Wrong value, try again");
checkValue = true;
}
}
return Integer.parseInt(line);
}
setString() works exactly the same scheme.
Method::getParameterTypes returns Class[].
So your code method.getParameterTypes().getClass() will always return [Ljava.lang.Class. try this code:
Class[] types = method.getParameterTypes();
if (types.length == 1 && types[0] == String.class) {
// your second condition...
}

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);

Rhino API - Access js method using org.mozilla.javascript.Context?

How can i access get method in this script:
(function( global ){
var Result;
(Result = function( val ) {
this.tpl = val || '' ;
}).prototype = {
get: function ()
{
return 'text' ;
}
};
global.Result = Result ;
} ( window ) ) ;
I tried in this way:
Create Window class and Result interface:
public interface Result{ public String get(); }
public class Window { public Result Result; }
Call js function:
public void call() {
Context context = Context.enter();
ScriptableObject scope = context.initStandardObjects();
FileReader fileReader = new FileReader("file.js");
Object window = Context.javaToJS(new Window(), scope);
scope.put("window", scope, window);
context.evaluateReader(scope, fileReader, "test", 1, null);
context.evaluateString(scope, "Result = window.Result;", "test", 2, null);
context.evaluateString(scope, "result = Result.get();", "test", 3, null);
Object result = scope.get("result", scope);
System.out.println("\n" + Context.toString(result));
context.exit();
}
but I can't get the return result from get function:
It worked for me:
public class Result extends ScriptableObject{
#Override
public String getClassName() {
// TODO Auto-generated method stub
return "Result";
}
}
public class Window extends ScriptableObject {
private Result Result;
public Result getResult() {
return Result;
}
#Override
public String getClassName() {
return "Window";
}
}
public void call() {
Context context = Context.enter();
ScriptableObject scope = context.initStandardObjects();
FileReader fileReader = new FileReader("file.js");
Window window = new Window();
scope.put("window", scope, window);
scope.put("window.Result", window.getResult());
context.evaluateReader(scope, fileReader, "test", 1, null);
context.evaluateString(scope, "Result = window.Result;", "test", 1, null);
context.evaluateString(scope, "var myResult = new Result();", "test", 1, null);
context.evaluateString(scope, "r = myResult.get();", "test", 1, null);
Object result = scope.get("r", scope);
System.out.println("\n" + Context.toString(result));
context.exit();
}

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