Re: Re: Re: Analyzer does not use stack map frames

Marcin Rzeźnicki <[email protected]>
Newsgroups gmane.comp.java.objectweb.asm
Message-ID <[email protected]>
2010/11/3 Marcin Rzeźnicki <[email protected]>:
> On Wed, Nov 3, 2010 at 5:31 PM, Eugene Kuleshov <[email protected]> wrote:

>>  Have you studied a corresponding chapter in the ASMs User Guide book?
>>  Basically the intent is that you can implement/extend the
>> Interpreter, leaving the Analyzer unchanged. There is some examples in
>> the mentioned book.
>
> Yes, but in fact you cannot do that. Just take a look at my example
> from the first mail. You cannot determine correctly type of the new
> value in merge. This design'd work truly great if either interpreter
> would have been given interface to do anything it needs or Analyzer
> would have been "good" enough to handle all cases. Here both are not:
> Interpreter cannot mask deficiencies of analysis algorithm because it
> even does not know whenever frame is encountered and merge method does
> not specify where it is being taken (if it had you could have looked
> it up yourself), Analyzer simply ignores this, and in practice is not
> extendable.So in practice it just cannot perform correct analysis in
> all cases, no matter what the intended pattern is. Could you give any
> advice on how to handle this? Many thanks.
>

Hi
I managed to add proper stack map handling to Analyzer - unfortunately
it is not doable via extending this class - mainly because analyze
method constructs Frame array which holds deduced frames so one cannot
prepare frames from stack maps in advance (analyze would throw it
away). I thought I could share this piece of code, maybe someone will
find it useful. The basic idea is to loop through the code before
"real' analysis takes place and prepare all these frames for which
stack map is known. After this has been done analysis will take care
of merging. I also found it useful to propagate precomputed frames
backward to label nodes before an instance of FrameNode (it seems
logical to me, after all - stack map frames are mostly placed after
some label - be it a jump target or exception handler). And one more
thing, in order to properly analyze frames I added private field
called stackMapTop which marks where stack map frame data ends (it is
needed for F-CHOP and F_APPEND frames, otherwise algorithm would not
know where to chop or append). This field is first set after original
code computes initial Frame from method arguments;

        for (int i = 0; i < args.length; ++i) {
            current.setLocal(local++, interpreter.newValue(args[i]));
            if (args[i].getSize() == 2) {
                current.setLocal(local++, interpreter.newValue(null));
            }
        }
        // addition: set stack map top
        stackMapTop = local;
        while (local < m.maxLocals) {
            current.setLocal(local++, interpreter.newValue(null));
        }

then I added this loop:

       // addition: iterate through all instructions and set frames for all
        // stack map frames (and for neighbour labels if there are any)
        Frame stackmapFrame = current;
        for (int ic = 0; ic < n; ic++) {
            final AbstractInsnNode insn = insns.get(ic);
            if (insn.getType() == AbstractInsnNode.FRAME) {
                stackmapFrame = newFrame((FrameNode) insn, stackmapFrame);
                merge(ic, stackmapFrame);
                int j = 1;
                AbstractInsnNode prevInsn = insn.getPrevious();
                while (prevInsn != null && prevInsn.getOpcode() == -1) {
                    prevInsn = prevInsn.getPrevious();
                    frames[ic - j] = stackmapFrame;
                    j += 1;
                }
            }
        }
        merge(0, current , null);

newFrame(FrameNode, Frame) is the new overload of newFrame which does the job;

   private Frame newFrame(FrameNode node, Frame previousFrame) {
        final Frame result = newFrame(previousFrame);
        switch (node.type) {
            case F_APPEND:
                setFrameLocals(result, node.local);
                result.clearStack();
            break;
            case F_CHOP:
                int newStackMapTop = stackMapTop - node.local.size();
                for (int i = newStackMapTop; i < stackMapTop; i++) {
                    result.setLocal(i, interpreter.newValue(null));
                }
                stackMapTop = newStackMapTop;
                result.clearStack();
            break;
            case F_FULL:
                stackMapTop = 0;
                setFrameLocals(result, node.local);
                int maxLocals = result.getLocals();
                for (int i = stackMapTop; i < maxLocals; i++) {
                    result.setLocal(i, interpreter.newValue(null));
                }
                List<Object> stack = node.stack;
                int nStack = stack.size();
                for (int i = 0; i < nStack; i++) {

result.push(interpreter.newValue(getTypeFromFrameOpcode(stack.get(i))));
                }
            break;
            case F_SAME:
                result.clearStack();
            break;
            case F_SAME1:
                result.clearStack();

result.push(interpreter.newValue(getTypeFromFrameOpcode(node.stack.get(0))));
            break;
            default:
                throw new CoroutineGenerationException("Expanded
frames not allowed");
        }
        return result;
    }

setFrameLocals and getTypeFromFrameOpcode are just helpers;

    private void setFrameLocals(final Frame frame, List<Object> locals) {
        int nLocals = locals.size();
        for (int i = 0; i < nLocals; i++) {
            Value newValue =
interpreter.newValue(getTypeFromFrameOpcode(locals.get(i)));
            frame.setLocal(stackMapTop, newValue);
            if (newValue.getSize() == 2) {
                frame.setLocal(stackMapTop + 1, interpreter.newValue(null));
                stackMapTop += 2;
            } else {
                stackMapTop += 1;
            }
        }
    }

    private static Type getTypeFromFrameOpcode(Object opcode) {
        if (opcode instanceof String) {
            return Type.getObjectType((String) opcode);
        }
        if (opcode == INTEGER) {
            return Type.INT_TYPE;
        }
        if (opcode == FLOAT) {
            return Type.FLOAT_TYPE;
        }
        if (opcode == LONG) {
            return Type.LONG_TYPE;
        }
        if (opcode == DOUBLE) {
            return Type.DOUBLE_TYPE;
        }
        if (opcode == NULL) {
            return Type.VOID_TYPE;
        }
        return null;
    }

and that's all - in my tests it seems to work fine. Hope this helps
someone. The main benefit is that this addition sets correctly types
of variables which is not possible in general with current Analyzer,
no matter how hard one would try to achieve this in Interpreter's
subclass. This code of course makes analysis little slower, but I did
not measure how much - I do not expect it to be really bad anyway.



-- 
Pozdrawiam
Marcin Rzeźnicki
message-footer.txt (text/plain, 238 B)
-- 
You receive this message as a subscriber of the [email protected] mailing list.
To unsubscribe: mailto:[email protected]
For general help: mailto:[email protected]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.