使用Javassist,如何将代码添加到空循环?

时间:2016-12-14 16:09:44

标签: javassist

使用Javassist 3.20.0.GA,我试图将代码注入空循环。

我尝试的所有内容,在尝试创建修改后的类的新实例时,我一直在运行java.lang.VerifyError错误。

我试图将问题隔离到无法成功运行的小程序。

if object_id('tempdb..#tt') is not null drop table #tt

create table #tt (name varchar(45), [status] varchar(16), [Date] varchar(16), [Time] varchar(16))

insert into #tt (name, [status], [date], [time]) values

('Employee 1','Check In','20/11/2016','03:52:15 PM'),
('Employee 1','Check Out','21/11/2016','12:08:50 AM'),
('Employee 1','Check In','21/11/2016','03:45:37 PM'),
('Employee 1','Check Out','22/11/2016','12:14:25 PM'),
('Employee 1','Check In','22/11/2016','03:41:34 PM'),
('Employee 1','Check Out','23/11/2016','12:04:53 PM')


;with cte as(
    select
        *,
        ROW_NUMBER() over (partition by [Name] order by convert(datetime,[Date] + ' ' + convert(varchar(8),cast([time] as time)),103)) as rownum
        from #tt)


select
    ci.Name,
    ci.[Date] as CheckInDate,
    ci.[Time] as CheckInTime,
    co.[Date] as CheckOutDate,
    co.[Time] as CheckOutTime
from
    cte ci
    inner join cte co on
    co.rownum = ci.rownum + 1
where ci.status = 'Check In'


--Results
Name        CheckInDate CheckInTime CheckOutDate CheckOutTime
Employee 1  20/11/2016  03:52:15 PM 21/11/2016   12:08:50 AM
Employee 1  21/11/2016  03:45:37 PM 22/11/2016   12:14:25 PM
Employee 1  22/11/2016  03:41:34 PM 23/11/2016   12:04:53 PM

当我运行此程序时,以下内容将写入控制台..

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.InstructionPrinter;
import javassist.bytecode.MethodInfo;
import javassist.compiler.Javac;

public class EmptyLoopTest {
    private void testEmptyLoopModification() throws Exception {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get(EmptyLoopTest.class.getName() + "$EmptyLoopClass");
        CtMethod m = cc.getDeclaredMethod("emptyLoopMethod");
        printMethod("Before modifications", m);

        MethodInfo methodInfo = m.getMethodInfo();
        CodeAttribute ca = methodInfo.getCodeAttribute();
        CodeIterator ci = ca.iterator();

        Javac jv = new Javac(cc);
        jv.compileStmnt("System.out.println(\"injected into loop\");");

        Bytecode b = jv.getBytecode();
        adjustCodeAttributeIfNeeded(b, ca);
        ci.insertAt(0, b.get());
        printMethod("After modifications", m);
        Class c = cc.toClass();

        logInfo("Attempting to create instance of modified class");
        c.newInstance();
    }
    private void adjustCodeAttributeIfNeeded(Bytecode b, CodeAttribute ca){
        int locals = b.getMaxLocals();
        int stack = b.getMaxStack();
        if(stack > ca.getMaxStack()) {
            ca.setMaxStack(stack);
        }
        if(locals > ca.getMaxLocals()) {
            ca.setMaxLocals(locals);
        }
    }

    private void printMethod(String title, CtMethod m){
        logInfo(title);
        InstructionPrinter instructionPrinter = new InstructionPrinter(System.out);
        instructionPrinter.print(m);
    }
    private void logInfo(String message){
        System.out.println("");
        System.out.println("------" + message);
    }

    public class EmptyLoopClass {
        public void emptyLoopMethod() {
            for(;;){
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // have errors written to sysout so all output from this program is in order
        System.setErr(System.out);
        new EmptyLoopTest().testEmptyLoopModification();
    }
}

如果一切都按预期工作,我会在修改到goto 0而不是它当前显示的内容之后期望goto指令,转到8.它就好像StackMapTable不是&#39 ;当我调用javassist.bytecode.CodeIterator#insertAt时适当调整。

有谁能发现我在这里做错了什么?

谢谢,

埃里克

1 个答案:

答案 0 :(得分:0)

  1. 你创建类似c.newInstance();的类,所以它应该有0参数构造函数 - 是静态内部类

    public static class EmptyLoopClass {
    public void emptyLoopMethod() {
        for(;;){
        }
    }
    
  2. 您应该在修改代码后致电methodInfo.rebuildStackMap(cp);。 JVM使用StackMap验证类文件。

  3. 无论如何,你最终会得到字节码

        3: ldc #34 = "injected into loop"
        5: invokevirtual #40 = Method java.io.PrintStream.println((Ljava/lang/String;)V)
        8: goto 8
    

    此代码不在循环内,它在循环之前。实际循环是单指令8: goto 8,如果要在其中注入指令,则必须生成一些额外的循环代码。